Simple C ++ getter / setters

Recently, I have been writing my getter and seters 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 does not allow me:

  • check the input for the setter (which is a big BUT),
  • return by value in the const member function (because I want the compiler to catch the assignment to const objects: ca.value() = 2 ). Update: see below cluracan.

However, I still use this a lot because

  • In most cases, I don’t need it,
  • This allows me to separate the implementation details of my classes from their interface, which is what I want.

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:

  • Are there any other shortcomings of this approach that I do not see?
  • Why do people prefer:
    • getter / seters methods with different names?
    • passing a value as a parameter? just to test input or are there other main reasons?
+4
source share
3 answers
  • As a rule, the use of accessories / mutators is not a constructive smell at all, since the open interface of your class is incomplete. Usually you want to use a useful public interface that provides meaningful functionality, not just get / set (which is one or two steps better than we were in C with structures and functions). Every time you want to write a mutator, and many times you want to write an accessory, first take a step back and ask yourself "do I *really* need this?" .
  • It's just that people with an idiom may not be ready to expect such a function, so it will increase the support time for finding the code.
  • The same methods are almost the same as the public member: just use the public member in this case. When methods do two different things, call them two different ways.
  • Returning “mutator” with a link other than const would allow us to use many problems with the alias when someone delays the alias of the member, relying on it to exist later. Using the separate setter function, you do not allow people to smooth your personal data.
+5
source

This approach does not allow me:

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

I do not understand what do you mean. If you mean what, I think, you mean, you will be pleasantly surprised :) Just try to return the constant member by value and see if you can do ca.value()=2 ...

But my main question is if you want some kind of input check, 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 of input! (one click '='). The only drawback to this is that you cannot pass a value by reference to a function that modifies it.

As for your second example after editing, with vector - using your receiver / setter makes even more sense than your original example, because you want to provide access to the values ​​(allow the user to change the values), but NOT to the vector (you don’t want to, so that the user can resize the vector).

So, although in the first example I would recommend making a public participant, in the second it is clearly not an option, and using this form of getters / setters is really a good option if input verification is not required.

Also, when I have classes like your second type (with a vector), I like to give access to begin and end iterators. This allows for more flexible use of data with standard tools (while not allowing the user to resize vector and allowing you to easily change the type of container)

Another advantage of this is that random access iterators have 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 can be pretty ugly that you still want your getters / setters in addition to this)

+5
source

REGARDING THE ENTRANCE OPTION: In your example, the assignment occurs in the calling code. If you want to validate user input, you need to pass a value that will be validated in your struct object. This means that you need to use member functions (methods). For instance,

 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 - these are methods under the hood.

0
source

Source: https://habr.com/ru/post/1495772/


All Articles