简体   繁体   中英

Reasons for defining non-const 'get' member functions?

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?

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:

1. Easy to instrument

Who 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 (eg storing or retrieving data in a database).

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.

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, ie 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.

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 .

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM