21

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?

Juri
  • 604
  • 9
  • 19
  • 9
    IMO even cleaner would be no getters or setters at all, and instead have the class be about behavior instead. If you have getters and setters for a member variable, you might as well make it `public`. – Some programmer dude Dec 08 '18 at 13:40
  • 1
    The primary reason for using a non-const get member function (or any getter and setter anti-pattern) is to avoid doing proper object-oriented programming. – Eljay Dec 08 '18 at 14:16
  • 2
    @Someprogrammerdude Not really. Maybe right now that field is just a plain value but you know that when you are going to iterate to develop new requirements it will have to be a computed value, and thus having a getter avoids having to change all the code that accesses the field. Also: having a getter you can *easily* add a breakpoint on it and check in a debugger where is the code that accesses it. If you have just a field you have to put breakpoints in every place that accesses the field which is much more cumbersome to do. – Bakuriu Dec 08 '18 at 14:49
  • A const getter makes sense **if the setter had to make some checks (i.e. establish invariants) before setting the value** which, in cases when it needs an invariant, is usually made private to allow modification through a constructor and a setter only (which would guarantee the invariants). In general, in my experience setters and getters, and especially non-const getters, are more often a result of oral and written tradition rather than a conclusion of thoughts specific to a particular case. However, notice that Stroustrup's example is a generic case meant to simply introduce the idea. – Leo Heinsaar Dec 09 '18 at 11:22
  • @Bakuriu I guess what Some was refering to is that other classes accessing private fields is bad design in the first place. Just a stupid example: bad design is to have a `customer::getMoney()` and `customer::setMoney(int)` when it actually should be a `customer::pay(int)`. All your concerns are covered by `pay` plus you dont break encapsulation – 463035818_is_not_an_ai Dec 13 '18 at 12:21
  • @user463035818 His comment explicitly stated that if you add a getter and a setter than you should simply make the field `public`. I gave reasons why this is often not the case, and having a `public` field is worse. Then if you can avoid exposing an internal field it's obvious that you should do that. – Bakuriu Dec 13 '18 at 17:49
  • @Bakuriu not providing public access to privates is better, but if you do provide access then it does not really matter whether this is via making the field public or providing getters and setters I am completely with Some on this point. Seems like you are not. Lets agree that we disagree ;), we wont solve it in comments anyhow. – 463035818_is_not_an_ai Dec 13 '18 at 17:54
  • @Bakuriu on a second though I realized that maybe the first comment refers to the fact that if the getter returns a reference as in OP's example then you are out of luck because you cannot change eg the type of the private member without effecting the public interface. Returning references is really just as good as making the member public. (Though I am a bit more extreme and try to avoid getters and setters always, maybe thst was part of our misunderstanding;) – 463035818_is_not_an_ai Dec 13 '18 at 18:06
  • @Bakuriu thanks for the dispute, you made me realize that i missed to adress an important issue of the question. We may have a different opinion, but I guess you can agree that returning a non-const reference is the opposite of encapsulation – 463035818_is_not_an_ai Dec 13 '18 at 19:52

5 Answers5

16

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&.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
9

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:

1. Easy to instrument

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.

2. Cultural convention / "the boss said so"

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.

3. Maybe 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).

4. Allows changing class internals later without changing user code

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.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 1
    "Attempt to mutate val when bad things have happened" is just incorrect. Selection of `const` or non-`const` member function depends on the qualifications of the `this` object, and not how the return value is being used. In particular, a non-`const` getter cannot assume mutation is occurring. – Ben Voigt Dec 09 '18 at 00:48
  • @BenVoigt: Suppose `S` is something `NuclearMissilePosition`, or `PatientInfusionValueState`. These are mutable, and should be mutable, but under certain conditions I might not want . Now, you could say "ok, only let friend functions mutate them then", or "write a setter function then", but this is also a way to do it. – einpoklum Dec 09 '18 at 10:42
  • But when you cannot distinguish between get and set, your error message should reflect that uncertainty, not claim mutation was attempted. – Ben Voigt Dec 10 '18 at 00:30
  • Ok, fair enough. – einpoklum Dec 11 '18 at 15:46
5

get() is callable by non const objects which are allowed to mutate, you can do:

S r(0);
r.get() = 1;

but if you make r const as const S r(0), the line r.get() = 1 no longer compile, not even to retrieve the value, that's why you need a const version const T& get() const to at least to able to retrieve the value for const objects, doing so allows you do:

const S r(0)
int val = r.get()

The const version of member functions try to be consistent with the constness property of the object the call is made on, i.e if the object is immutable by being const and the member function returns a reference, it may reflect the constness of the caller by returning a const reference, thus preserving the immutability property of the object.

Jans
  • 11,064
  • 3
  • 37
  • 45
  • 1
    OP was asking, I'm sure, why have this method rather than just giving access to the member itself. – einpoklum Dec 08 '18 at 14:13
  • 1
    @einpoklum He was more interested in the reason and *uses cases* of having two different versions of `get` to perform the same action "return val" ... check at his last 2 paragraphs. – Jans Dec 08 '18 at 14:17
3

It depends on the purpose of S. If it's some kind of a thin wrapper, it might be appropriate to allow the user to access the underlaying value directly.

One of the real-life examples is std::reference_wrapper.

Igor R.
  • 14,716
  • 2
  • 49
  • 83
  • This is likely the "spot on" answer. Useful when encapsulating C types for external libraries and such. – pipe Dec 08 '18 at 17:16
  • In my opinion, `reference_wrapper` does a different thing. In OP's example, with `get()`, you can modify the value of the member. With `reference_wrapper::get`, you cannot modify which object it points to, you can only modify the pointed object. – geza Dec 08 '18 at 20:20
  • @geza reference_wrapper exposes reference semantics, so its "value" is the wrapped object. The fact it's hold by pointer is just an implementation detail. – Igor R. Dec 08 '18 at 21:29
  • That's why it is not relevant here. In OP's example, `get()` returns a reference to a member, which is contained within the object. `reference_wrapper` doesn't do this., its `get()` function does something else. "Is there any case where it makes sense to have non-const get function that returns val?". `reference_wrapper` returns the value pointed to, not the value itself. – geza Dec 08 '18 at 21:36
1

No. If a getter simply returns a non-const reference to a member, like this:

private:
    Object m_member;

public:
    Object &getMember() {
         return m_member;
    }

Then m_member should be public instead, and the accessor is not needed. There is absolutely no point making this member private, and then create an accessor, which gives all access to it.

If you call getMember(), you can store the resulting reference to a pointer/reference, and afterwards, you can do whatever you want with m_member, the enclosing class will know nothing about it. It's the same, as if m_member had been public.

Note, that if getMember() does some additional task (for example, it doesn't just simply return m_member, but lazily constructs it), then getMember() could be useful:

Object &getMember() {
    if (!m_member) m_member = new Object;
    return *m_member;
}
geza
  • 28,403
  • 6
  • 61
  • 135