views:

585

answers:

11

Have a simple container class:

public Container 
{
   public:
   Container() {}

   Container(const Container& cont)          //option 1
   { 
       SetMyString(cont.GetMyString());
   }

   //OR

   Container(const Container& cont)          //option 2
   {
      m_str1 = cont.m_str1;
   }

   public string GetMyString() { return m_str1;}       

   public void SetMyString(string str) { m_str1 = str;}

   private:

   string m_str1;
}

So, would you recommend this method or accessing the member variables directly?

  • In the example, all code is inline, but in our real code there is no inline code.

Update (29 Sept 09):

Some of these answers are well written however they seem to get missing the point of this question:

  • this is simple contrived example to discuss using getters/setters vs variables

  • initializer lists or private validator functions are not really part of this question. I'm wondering if either design will make the code easier to maintain and expand.

  • Some ppl are focusing on the string in this example however it is just an example, imagine it is a different object instead.

  • I'm not concerned about performance. we're not programming on the PDP-11

A: 

I prefer using an interface for outer classes to access the data, in case you want to change the way it's retrieved. However, when you're within the scope of the class and want to replicate the internal state of the copied value, I'd go with data members directly.

Not to mention that you'll probably save a few function calls if the getter are not inlined.

Joce
A: 

If your getters are (inline and) not virtual, there's no pluses nor minuses in using them wrt direct member access -- it just looks goofy to me in terms of style, but, no big deal either way.

If your getters are virtual, then there is overhead... but nevertheless that's exactly when you DO want to call them, just in case they're overridden in a subclass!-)

Alex Martelli
inline is a HINT to the ccompiler; the compiler doesn't have to actually copy the code to the call site, and in many cases (depending on the particular compiler) won't. NEVER assume that the inline keyword means that the code is actually inline. (And that can be a good thing: inline can be faster, but it definitely means more code bloat.)
tpdi
"If your getters are virtual, then there is overhead... but nevertheless that's exactly when you DO want to call them, just in case they're overridden in a subclass!-)" But the OP is talking about a ctor, and in ctors, overrides are not called, as at the time of construction, the class type is the class under construction, not the (possible) derived class calling the base class ctor.
tpdi
@tpdi, in a copy ctor, the overrides of (the getters of, in this case) the object you're copying from WILL be called -- you may be confusing the issue with overrides on _the object you're constructing_ (typically setters, in a copy ctor) which indeed would not be called!
Alex Martelli
Assume not virtual.
cbrulak
@cbrulak, then, as I said, no advantage in calling the getters -- probably no disadvantage either (unless your compiler perversely refuses to inline small methods as @tpdi suggests might happen;-), but most definitely no advantage, so, why bother?
Alex Martelli
+11  A: 

EDIT: Answering the edited question :)

this is simple contrived example to discuss using getters/setters vs variables

If you have a simple collection of variables, that don't need any kind of validation, nor additional processing then you might consider using a POD instead. From Stroustrup's FAQ:

A well-designed class presents a clean and simple interface to its users, hiding its representation and saving its users from having to know about that representation. If the representation shouldn't be hidden - say, because users should be able to change any data member any way they like - you can think of that class as "just a plain old data structure"

In short, this is not JAVA. you shouldn't write plain getters/setters because they are as bad as exposing the variables them selves.

initializer lists or private validator functions are not really part of this question. I'm wondering if either design will make the code easier to maintain and expand.

If you are copying another object's variables, then the source object should be in a valid state. How did the ill formed source object got constructed in the first place?! Shouldn't constructors do the job of validation? aren't the modifying member functions responsible of maintaining the class invariant by validating input? Why would you validate a "valid" object in a copy constructor?

I'm not concerned about performance. we're not programming on the PDP-11

This is about the most elegant style, though in C++ the most elegant code has the best performance characteristics usually.


You should use an initializer list. In your code, m_str1 is default constructed then assigned a new value. Your code could be something like this:

class Container 
{
public:
   Container() {}

   Container(const Container& cont) : m_str1(cont.m_str1)
   { }

   string GetMyString() { return m_str1;}       
   void SetMyString(string str) { m_str1 = str;}
private:
   string m_str1;
};


@cbrulak You shouldn't IMO validate cont.m_str1 in the copy constructor. What I do, is to validate things in constructors. Validation in copy constructor means you you are copying an ill formed object in the first place, for example:

Container(const string& str) : m_str1(str)
{
    if(!valid(m_str1)) // valid() is a function to check your input
    {
     // throw an exception!
    }
}
AraK
then how would you handle validating the input or other logic in the setter functions?
cbrulak
When copying an object, the source object should be a valid one.
AraK
You could write a private validation function and call it in the copy constructor body and the setter function
mocj
There is other login in getters and setters than just logic. Logging for example? I prefer not to make assumptions like that.
cbrulak
The setter function is for unknown input to come from outside of the class, when copying from this known good source there should be no need to recheck the data.The class and it's state of correctness is something you need to manage in the class, it is likely to be appropriate to re implement some of that logic in a different way between the copy and the assignment.
Greg Domjan
Operations on a class should more often than not be an action causing a composite event, otherwise your class is devolving to being a datastore cache.Your example of logging - I would want to log 'copy' differently to 'set' personally, and certainly not want to see N log lines for a copy causing the setting of N values.
Greg Domjan
how does an initializer list address the issue? I'm talking about the design of class. Futhermore, the initializer list doesn't address a non-trivial member like an object or something more complex like a State object. I don't really see how this answer actually addresses the main issue here.
cbrulak
@cbrulak I think your example wasn't clear enough. Initialization is done in the initializer list not in the body of constructors. If you need to validate something you could write a helper function to do that, and apply it in the initializer list, just like what dcw did.
AraK
@cbrulak Whatever complexity you are dealing with, your objects that are initialized in the initialization list should throw exceptions in case of ill formed input. Otherwise your object should be in a valid state.
AraK
+7  A: 

You should use an initializer list, and then the question becomes meaningless, as in:

Container(const Container& rhs)
  : m_str1(rhs.m_str1)
{}

There's a great section in Matthew Wilson's Imperfect C++ that explains all about Member Initializer Lists, and about how you can use them in combination with const and/or references to make your code safer.

Edit: an example showing validation and const:

class Container
{
public:
  Container(const string& str)
    : m_str1(validate_string(str))
  {}
private:
  static const string& validate_string(const string& str)
  {
    if(str.empty())
    {
      throw runtime_error("invalid argument");
    }
    return str;
  }
private:
  const string m_str1;
};
dcw
how would you handle validating the input or other logic in the setter functions?
cbrulak
@cbrulak: there's a bunch of stuff in Imperfect C++ about this. I'll edit my reply to give an example ...
dcw
the 'validating' is a moot point, if certain values are invalid for m_str1, there should never exist such an object in the first place. At most you could assert to catch errors in the validating process via the setters and (regular) constructors.
Pieter
+1  A: 

Ask yourself what the costs and benefits are.

Cost: higher runtime overhead. Calling virtual functions in ctors is a bad idea, but setters and getters are unlikely to be virtual.

Benefits: if the setter/getter does something complicated, you're not repeating code; if it does something unintuitive, you're not forgetting to do that.

The cost/benefit ratio will differ for different classes. Once you're ascertained that ratio, use your judgment. For immutable classes, of course, you don't have setters, and you don't need getters (as const members and references can be public as no one can change/reseat them).

tpdi
If the setter/getter does something complicated, it should already have been done to the source object, or you have a bug. If a copy constructor does anything beyond the semantic equivalent of duplicating the data of the source object, you're hiding a bug.
Pieter
+1  A: 

Do you anticipate how the string is returned, eg. white space trimmed, null checked, etc.? Same with SetMyString(), if the answer is yes, you are better off with access methods since you don't have to change your code in zillion places but just modify those getter and setter methods.

Murali VP
I see what you're trying to say, but for a copy constructor that's a moot point. If an object exists with a string, it should already be white space trimmed, null checked, etc. by the regular constructors and setters. If a copy constructor needs to change a member in any way, that means the original object was wrong, and hence there's a bug in a regular constructor or setter. Said bug should not be hidden by the copy constructor!
Pieter
Why is a moot point? See option 1 of OP. My answer doesn't necessarily apply to just strings, as you may know that was just an example. What if the class wants to count the number of times get/set is called meaning accessed/assigned/updated?
Murali VP
A: 
Jerry Coffin
I'm not really sure what you are answering:1) You focusing on the string of a contrived example. imagine this is an object or pointer or something else. We're discussing a concept of design not a concrete implementation. 2) How are the overloaded operators related to my question?
cbrulak
The question you asked was:"So, would you recommend this method or accessing the member variables directly?" My answer, summarized, was: you should usually access the member variables directly. If you have a situation where the getter and/or setter really DO something, you should still use a public variable, but move that functionality into a separate class that creates a type that enforces the constraints you'd try to put into the getter/setter -- and it should do so by overloading operations (most often operator=) to avoid ugly syntax and really enforce what a setter only hopes to.
Jerry Coffin
+1  A: 
phoku
A: 

There is a simple test that works for many design questions, this one included: add side-effects and see what breaks.

Suppose setter not only assigns a value, but also writes audit record, logs a message or raises an event. Do you want this happen for every property when copying object? Probably not - so calling setters in constructor is logically wrong (even if setters are in fact just assignments).

ima
A: 

Although I agree with other posters that there are many entry-level C++ "no-no's" in your sample, putting that to the side and answering your question directly:

In practice, I tend to make many but not all of my member fields* public to start with, and then move them to get/set when needed.

Now, I will be the first to say that this is not necessarily a recommended practice, and many practitioners will abhor this and say that every field should have setters/getters.

Maybe. But I find that in practice this isn't always necessary. Granted, it causes pain later when I change a field from public to a getter, and sometimes when I know what usage a class will have, I give it set/get and make the field protected or private from the start.

YMMV

RF

  • you call fields "variables" - I encourage you to use that term only for local variables within a function/method
Roark Fan
fields vs variables: sorry, not clear. I've never heard anyone use the 'fields' for C++ class members.
cbrulak
If you make member variables public, you're not using classes correctly. Same if you think all non-public members should have getters and setters. You are neither answering the question (which admittedly doesn't have a good answer) nor advocating good coding practice. You're also insisting on highly nonstandard terminology. Where are you coming from?
David Thornley
"If you make member variables public, you're not using classes correctly" - patently not true. It is a valid use of classes, just like structs. Granted, it is not a scalable/good one when programming in the medium or the large, but "incorrect" is just not true."Highly nonstandard terminology" - hardly. You can't tell me that "variables" is a good name for a class member attribute. "Field" was probably a poor choice, admittedly, but "member" means "method" to some, and I wanted to disambiguate.
Roark Fan
+1  A: 

Why do you need getters and setters at all?

Simple :) - They preserve invariants - i.e. guarantees your class makes, such as "MyString always has an even number of characters".

If implemented as intended, your object is always in a valid state - so a memberwise copy can very well copy the members directly without fear of breaking any guarantee. There is no advantage of passing already validated state through another round of state validation.

As AraK said, the best would be using an initializer list.


Not so simple (1):
Another reason to use getters/setters is not relying on implementation details. That's a strange idea for a copy CTor, when changing such implementation details you almost always need to adjust CDA anyway.


Not so simple (2):
To prove me wrong, you can construct invariants that are dependent on the instance itself, or another external factor. One (very contrieved) example: "if the number of instances is even, the string length is even, otherwise it's odd." In that case, the copy CTor would have to throw, or adjust the string. In such a case it might help to use setters/getters - but that's not the general cas. You shouldn't derive general rules from oddities.

peterchen