3

I have a simple container class with a copy constructor.

Do you recommend using getters and setters, or accessing the member variables directly?

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;
}
  • 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

mskfisher
  • 3,291
  • 4
  • 35
  • 48
cbrulak
  • 15,436
  • 20
  • 61
  • 101
  • 1
    The right answer, for copy constructors, is initializer lists. Your question title says "copy constructors", and your examples are copy constructors. You are, in effect, asking which is the best tool to drive in a nail: a wrench or a pliers; and then you are arguing that you aren't interested in hammers. – David Thornley Sep 29 '09 at 16:26
  • not at all. I'm asking whether you would/should use Getters & Setters or directly access member vars. (If so, how you do that via assignment or init lists is irrelevant for this discussion) – cbrulak Sep 29 '09 at 16:34

11 Answers11

11

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!
    }
}
Khaled Alshaya
  • 94,250
  • 39
  • 176
  • 234
  • then how would you handle validating the input or other logic in the setter functions? – cbrulak Sep 29 '09 at 02:15
  • 2
    When copying an object, the source object should be a valid one. – Khaled Alshaya Sep 29 '09 at 02:18
  • You could write a private validation function and call it in the copy constructor body and the setter function – mocj Sep 29 '09 at 02:21
  • There is other login in getters and setters than just logic. Logging for example? I prefer not to make assumptions like that. – cbrulak Sep 29 '09 at 02:21
  • 1
    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 Sep 29 '09 at 02:23
  • 2
    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 Sep 29 '09 at 02:27
  • 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 Sep 29 '09 at 15:23
  • @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. – Khaled Alshaya Sep 29 '09 at 15:52
  • @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. – Khaled Alshaya Sep 29 '09 at 15:57
8

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
  • 3,481
  • 2
  • 22
  • 30
  • 1
    how would you handle validating the input or other logic in the setter functions? – cbrulak Sep 29 '09 at 02:15
  • 1
    @cbrulak: there's a bunch of stuff in Imperfect C++ about this. I'll edit my reply to give an example ... – dcw Sep 29 '09 at 02:21
  • 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 Sep 29 '09 at 09:14
2

As it's written right now (with no qualification of the input or output) your getter and setter (accessor and mutator, if you prefer) are accomplishing absolutely nothing, so you might as well just make the string public and be done with it.

If the real code really does qualify the string, then chances are pretty good that what you're dealing with isn't properly a string at all -- instead, it's just something that looks a lot like a string. What you're really doing in this case is abusing the type system, sort of exposing a string, when the real type is only something a bit like a string. You're then providing the setter to try to enforce whatever restrictions the real type has compared to a real string.

When you look at it from that direction, the answer becomes fairly obvious: rather than a string, with a setter to make the string act like some other (more restricted) type, what you should be doing instead is defining an actual class for the type you really want. Having defined that class correctly, you make an instance of it public. If (as seems to be the case here) it's reasonable to assign it a value that starts out as a string, then that class should contain an assignment operator that takes a string as an argument. If (as also seems to be the case here) it's reasonable to convert that type to a string under some circumstances, it can also include cast operator that produces a string as the result.

This gives a real improvement over using a setter and getter in a surrounding class. First and foremost, when you put those in a surrounding class, it's easy for code inside that class to bypass the getter/setter, losing enforcement of whatever the setter was supposed to enforce. Second, it maintains a normal-looking notation. Using a getter and a setter forces you to write code that's just plain ugly and hard to read.

One of the major strengths of a string class in C++ is using operator overloading so you can replace something like:

strcpy(strcat(filename, ".ext"));

with:

filename += ".ext";

to improve readability. But look what happens if that string is part of a class that forces us to go through a getter and setter:

some_object.setfilename(some_object.getfilename()+".ext");

If anything, the C code is actually more readable than this mess. On the other hand, consider what happens if we do the job right, with a public object of a class that defines an operator string and operator=:

some_object.filename += ".ext";

Nice, simple and readable, just like it should be. Better still, if we need to enforce something about the string, we can inspect only that small class, we really only have to look one or two specific, well-known places (operator=, possibly a ctor or two for that class) to know that it's always enforced -- a totally different story from when we're using a setter to try to do the job.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • 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 Sep 29 '09 at 15:17
  • 1
    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 Sep 29 '09 at 16:01
1

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
  • 34,554
  • 11
  • 80
  • 120
  • 2
    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 Sep 29 '09 at 09:28
1

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
  • 6,198
  • 4
  • 28
  • 36
  • 2
    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 Sep 29 '09 at 09:18
  • 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 Sep 29 '09 at 16:29
1

There's no silver bullet as how to write the copy constructor. If your class only has members which provide a copy constructor that creates instances which do not share state (or at least do not appear to do so) using an initializer list is a good way.

Otherwise you'll have to actually think.

struct alpha {
   beta* m_beta;
   alpha() : m_beta(new beta()) {}
   ~alpha() { delete m_beta; }
   alpha(const alpha& a) {
     // need to copy? or do you have a shared state? copy on write?
     m_beta = new beta(*a.m_beta);
     // wrong
     m_beta = a.m_beta;
   }

Note that you can get around the potential segfault by using smart_ptr - but you can have a lot of fun debugging the resulting bugs.

Of course it can get even funnier.

  • Members which are created on demand.
  • new beta(a.beta) is wrong in case you somehow introduce polymorphism.

... a screw the otherwise - please always think when writing a copy constructor.

phoku
  • 2,082
  • 1
  • 18
  • 16
1

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
  • 40,917
  • 20
  • 104
  • 186
0

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
  • 9,624
  • 19
  • 56
  • 74
0

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
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 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 Sep 29 '09 at 02:14
  • 1
    "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 Sep 29 '09 at 02:16
  • 1
    @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 Sep 29 '09 at 02:19
  • @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 Sep 29 '09 at 20:49
0

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
  • 8,105
  • 3
  • 20
  • 19
-2

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
  • 672
  • 8
  • 9
  • fields vs variables: sorry, not clear. I've never heard anyone use the 'fields' for C++ class members. – cbrulak Sep 29 '09 at 16:16
  • 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 Sep 29 '09 at 16:31
  • "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 Sep 29 '09 at 18:14