gnzlbg
gnzlbg

Reputation: 7415

Simple C++ getter/setters

Lately I'm writing my getter and setters as (note: real classes do more things in getter/setter):

struct A {
  const int& value() const { return value_; } // getter
        int& value()       { return value_; } // getter/setter
 private:
  int value_;
};

which allows me to do the following:

auto a = A{2}; // non-const object a

// create copies by "default" (value always returns a ref!):
int b = a.value();  // b = 2, is a copy of value :)
auto c = a.value(); // c = 2, is a copy of value :)

// create references explicitly:
auto& d = a.value();  // d is a ref to a.value_ :)
decltype(a.value()) e = a.value(); // e is a ref to a.value_ :)
a.value() = 3; // sets a.value_ = 3 :)

cout << b << " " << c << " " << d << " " << e << endl; // 2 2 3 3

const auto ca = A{1};
const auto& f = ca.value();  // f is a const ref to ca.value_ :)
auto& g = ca.value(); // no compiler error! :(
// g = 4; // compiler error :)
decltype(ca.value()) h = ca.value(); // h is a const ref to ca.value_ :)
//ca.value() = 2; // compiler error! :)

cout << f << " " << g << " " << h << endl; // 1 1 1

This approach doesn't allow me to:

However, I'm still using this a lot because

Example:

struct A {
  const int& value(const std::size_t i) const { return values_[i]; }
        int& value(const std::size_t i)       { return values_[i]; }
  private:
   std::vector<int> values_; 
   // Storing the values in a vector/list/etc is an implementation detail.
   // - I can validate the index, but not the value :(
   // - I can change the type of values, without affecting clients :)
 }; 

Now to the questions:

Upvotes: 5

Views: 4578

Answers (3)

Mark B
Mark B

Reputation: 96241

  • Generally using accessors/mutators at all is a design smell that your class public interface is incomplete. Typically speaking you want a useful public interface that provides meaningful functionality rather than simply get/set (which is just one or two steps better than we were in C with structs and functions). Every time you want to write a mutator, and many times you want to write an accessor first just take a step back and ask yourself "do I *really* need this?".
  • Just idiom-wise people may not be prepared to expect such a function so it will increase a maintainer's time to grok your code.
  • The same-named methods are almost the same as the public member: just use a public member in that case. When the methods do two different things, name them two different things.
  • The "mutator" returning by non-const reference would allow for a wide variety of aliasing problems where someone stashes off an alias to the member, relying on it to exist later. By using a separate setter function you prevent people from aliasing to your private data.

Upvotes: 6

rabensky
rabensky

Reputation: 2934

This approach doesn't allow me to:

  • return by value in the const member function (because I want the compiler to catch assignment to const objects ca.value() = 2).

I don't get what you mean. If you mean what I think you mean - you're going to be pleasantly surprised :) Just try to have the const member return by value and see if you can do ca.value()=2...

But my main question, if you want some kind of input validation, why not use a dedicated setter and a dedicated getter

struct A {
  int  value() const { return value_; } // getter
  void value(int v)  { value_=v; }      // setter
 private:
  int value_;
};

It will even reduce the amount typing! (by one '=') when you set. The only downside to this is that you can't pass the value by reference to a function that modifies it.

Regarding your second example after the edit, with the vector - using your getter/setter makes even more sense than your original example as you want to give access to the values (allow the user to change the values) but NOT to the vector (you don't want the user to be able to change the size of the vector).

So even though in the first example I really would recommend making the member public, in the second one it is clearly not an option, and using this form of getters / setters really is a good option if no input validation is needed.

Also, when I have classes like your second type (with the vector) I like giving access to the begin and end iterators. This allows more flexibility of using the data with standard tools (while still not allowing the user to change the vector size, and allowing easy change in container type)

Another bonus to this is that random access iterators have an operator[] (like pointers) so you can do

vector<int>::iterator       A::value_begin()     {return values_.begin();}
vector<int>::const_iterator A::value_begin()const{return values_.begin();}
...
a.value_begin()[252]=3;
int b=a.value_begin()[4];
vector<int> c(a.value_begin(),a.value_end())

(although it maybe ugly enough that you'd still want your getters/setters in addition to this)

Upvotes: 5

Malganis
Malganis

Reputation: 338

REGARDING INPUT VALIDATION: In your example, the assignment happens in the calling code. If you want to validate user input, you need to pass the value to be validated into your struct object. This means you need to use member functions (methods). For example,

struct A {
  // getter
  int& getValue() const { return value_; }

  // setter
  void setValue(const int& value) {
    // validate value here
    value_ = value;
  }

  private:
    int value_;
};

By the way, .NET properties are implemented are methods under the hood.

Upvotes: 0

Related Questions