I'm working on learning C++ with Stroustrup's (Programming Principles & Practice Using C++) book. In an exercise we define a simple struct:
template<typename T> struct S { explicit S(T v):val{v} { }; T& get(); const T& get() const; void set(T v); void read_val(T& v); T& operator=(const T& t); // deep copy assignment private: T val; };
We're then asked to define a const and a non-const member function to get val
.
I was wondering: Is there any case where it makes sense to have non-const get
function that returns val
?
It seems much cleaner to me that we can't change the value in such situations indirectly. What might be use cases where you need a const and a non-const get
function to return a member variable?
const function use cases A const function can be called by either a const or non- const object. Only a non- const object can call a non- const function; a const object cannot call it.
The non-const-qualified member function will be called if the member function is called on an object that is not const-qualified. For example: MyStack s; s. top(); // calls non-const member function const MyStack t; t. top(); // calls const member function.
The const member functions are the functions which are declared as constant in the program. The object called by these functions cannot be modified. It is recommended to use const keyword so that accidental changes to object are avoided. A const member function can be called by any type of object.
If the function is non-constant, then the function is allowed to change values of the object on which it is being called. So the compiler doesn't allow to create this chance and prevent you to call a non-constant function on a constant object, as constant object means you cannot change anything of it anymore.
Non-const getters?
Getters and setters are merely convention. Instead of providing a getter and a setter, a sometimes used idiom is to provide something along the line of
struct foo { int val() const { return val_; } int& val() { return val_; } private: int val_; };
Such that, depending on the constness of the instance you get a reference or a copy:
void bar(const foo& a, foo& b) { auto x = a.val(); // calls the const method returning an int b.val() = x; // calls the non-const method returning an int& };
Whether this is good style in general is a matter of opinion. There are cases where it causes confusion and other cases where this behaviour is just what you would expect (see below).
In any case, it is more important to design the interface of a class according to what the class is supposed to do and how you want to use it rather than blindly following conventions about setters and getters (eg you should give the method a meaningful name that expresses what it does, not just in terms of "pretend to be encapsulated and now provide me access to all your internals via getters", which is what using getters everywhere actually means).
Concrete example
Consider that element access in containers is usually implemented like this. As a toy example:
struct my_array { int operator[](unsigned i) const { return data[i]; } int& operator[](unsigned i) { return data[i]; } private: int data[10]; };
It is not the containers job to hide the elements from the user (even data
could be public). You dont want different methods to access elements depending on whether you want to read or write the element, hence providing a const
and a non-const overload makes perfectly sense in this case.
non-const reference from get vs encapsulation
Maybe not that obvious, but it is a bit controversial whether providing getters and setters supports encapsulation or the opposite. While in general this matter is to a large extend opinion based, for getters that return non const references it is not so much about opinions. They do break encapuslation. Consider
struct broken { void set(int x) { counter++; val = x; } int& get() { return x; } int get() const { return x; } private: int counter = 0; int value = 0; };
This class is broken as the name suggests. Clients can simply grab a reference and the class has no chance to count the number of times the value is modified (as the set
suggests). Once you return a non-const reference then regarding encapsulation there is little difference to making the member public. Hence, this is used only for cases where such behaviour is natural (eg container).
PS
Note that your example returns a const T&
rather than a value. This is reasonable for template code, where you dont know how expensive a copy is, while for an int
you wont gain much by returning a const int&
instead of an int
. For the sake of clarity I used non-template examples, though for templated code you would probably rather return a const T&
.
First let me rephrase your question:
Why have a non-const getter for a member, rather than just making the member public?
Several possible reasons reasons:
Whoever said the non-const getter needs to be just:
T& get() { return val; }
? it could well be something like:
T& get() { if (check_for_something_bad()) { throw std::runtime_error{ "Attempt to mutate val when bad things have happened"); } return val; }
However, as @BenVoigt suggests, it is more appropriate to wait until the caller actually tries to mutate the value through the reference before spewing an error.
Some organizations enforce coding standards. These coding standards are sometimes authored by people who are possibly overly-defensive. So, you might see something like:
Unless your class is a "plain old data" type, no data members may be public. You may use getter methods for such non-public members as necessary.
and then, even if it makes sense for a specific class to just allow non-const access, it won't happen.
val
just isn't there?You've given an example in which val
actually exists in an instance of the class. But actually - it doesn't have to! The get()
method could return some sort of a proxy object, which, upon assignment, mutation etc. performs some computation (e.g. storing or retrieving data in a database; or flipping a bit, which itself is not addressable like an object needs to be).
Now, reading items 1. or 3, above, you might ask "but my struct S
does have val
!" or "by my get()
doesn't do anything interesting!" - well, true, they don't; but you might want to change this behavior in the future. Without a get()
, all of your class' users will need to change their code. With a get()
, you only need to make changes to the implementation of struct S
.
Now, I don't advocate for this kind of a design approach approach, but some programmers do.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With