How to write C++ getters and setters

If I need to write a setter and/or getter for I write it like this:

struct X { /*...*/};

class Foo
{
private:
    X x_;

public:
    void set_x(X value)
    {
        x_ = value;
    }
    X get_x()
    {
        return x_;
    }
};

However I have heard that this is the Java style of writing setters and getters and that I should write it in C++ style. Moreover I was told it is ineficient and even incorrect. What does that mean? How can I write the setters and getters in C++?


Assume the need for getters and/or setters is justified. E.g. maybe we do some checks in the setter, or maybe we write only the getter.

There has been a lot of chatter about not needing getters and setters. While I agree with most of what's been said here, I still avocate for the need to know how to idiomatically write such methods because there are legitimate reasons where getters and setters are the right solution. They might not look at first glance as a setter or getter but they are, or at least the pattern for writing them applies.

E.g.:

  • Getting the size of a vector. You don't want to expose a data member, because it needs to be read only.

  • Getters and setters don't need to just expose a data member. Think about getting and setting an element of an array. There is logic there, you can't just expose a data member, there is no data member to expose in the first place. It's still a getter/setter pair you can't avoid:

    class Vector
    {
        void set_element(std::size_t index, int new_value);
        int get_element(std::size_t index);
    };
    

    Knowing the C++ idiomatic way of writing getters and setters will allow me to write the above get_element/set_element in a C++ idiomatic way.


There are two distinct forms of "properties" that turn up in the standard library, which I will categorise as "Identity oriented" and "Value oriented". Which you choose depends on how the system should interact with Foo. Neither is "more correct".

Identity oriented

class Foo
{
     X x_;
public:
          X & x()       { return x_; }
    const X & x() const { return x_; }
}

Here we return a reference to the underlying X member, which allows both sides of the call site to observe changes initiated by the other. The X member is visible to the outside world, presumably because it's identity is important. It may at first glance look like there is only the "get" side of a property, but this is not the case if X is assignable.

 Foo f;
 f.x() = X { ... };

Value oriented

class Foo
{
     X x_;
public:
     X x() const { return x_; }
     void x(X x) { x_ = std::move(x); }
}

Here we return a copy of the X member, and accept a copy to overwrite with. Later changes on either side do not propagate. Presumably we only care about the value of x in this case.


Over the years, I've come to believe that the whole notion of getter/setter is usually a mistake. As contrary as it may sound, a public variable is normally the correct answer.

The trick is that the public variable should be of the correct type. In the question you've specified that either we've written a setter that does some checking of the value being written, or else that we're only writing a getter (so we have an effectively const object).

I would say that both of those are basically saying something like: "X is an int. Only it's not really an int--it's really something sort of like an int, but with these extra restrictions..."

And that brings us to the real point: if a careful look at X shows that it's really a different type, then define the type that it really is, and then create it as a public member of that type. The bare bones of it might look something like this:

template <class T>
class checked {
    T value;
    std::function<T(T const &)> check;

public:
    template <class checker>
    checked(checker check) 
        : check(check)
        , value(check(T())) 
    { }

    checked &operator=(T const &in) { value = check(in); return *this; }

    operator T() const { return value; }

    friend std::ostream &operator<<(std::ostream &os, checked const &c) {
        return os << c.value;
    }

    friend std::istream &operator>>(std::istream &is, checked &c) {
        try {
            T input;
            is >> input;
            c = input;
        }
        catch (...) {
            is.setstate(std::ios::failbit);
        }
        return is;
    }
};

This is generic, so the user can specify something function-like (e.g., a lambda) that assures the value is correct--it might pass the value through unchanged, or it might modify it (e.g., for a saturating type) or it might throw an exception--but if it doesn't throw, what it returns must be a value acceptable for the type being specified.

So, for example, to get an integer type that only allows values from 0 to 10, and saturates at 0 and 10 (i.e., any negative number becomes 0, and any number greater than 10 becomes 10, we might write code on this general order:

checked<int> foo([](auto i) { return std::min(std::max(i, 0), 10); });

Then we can do more or less the usual things with a foo, with the assurance that it will always be in the range 0..10:

std::cout << "Please enter a number from 0 to 10: ";
std::cin >> foo; // inputs will be clamped to range

std::cout << "You might have entered: " << foo << "\n";

foo = foo - 20; // result will be clamped to range
std::cout << "After subtracting 20: " << foo;

With this, we can safely make the member public, because the type we've defined it to be is really the type we want it to be--the conditions we want to place on it are inherent in the type, not something tacked on after the fact (so to speak) by the getter/setter.

Of course, that's for the case where we want to restrict the values in some way. If we just want a type that's effectively read-only, that's much easier--just a template that defines a constructor and an operator T, but not an assignment operator that takes a T as its parameter.

Of course, some cases of restricted input can be more complex. In some cases, you want something like a relationship between two things, so (for example) foo must be in the range 0..1000, and bar must be between 2x and 3x foo. There are two ways to handle things like that. One is to use the same template as above, but with the underlying type being a std::tuple<int, int>, and go from there. If your relationships are really complex, you may end up wanting to define a separate class entirely to define the objects in that complex relationship.

Summary

Define your member to be of the type you really want, and all the useful things the getter/setter could/would do get subsumed into the properties of that type.


This is how I would write a generic setter/getter:

class Foo
{
private:
    X x_;

public:
    X&       x()        { return x_; }
    const X& x() const  { return x_; }
};

I will try to explain the reasoning behind each transformation:

The first issue with your version is that instead of passing around values you should pass const references. This avoids the needless copying. True, since C++11 the value can be moved, but that is not always possible. For basic data types (e.g. int) using values instead of references is OK.

So we first correct for that.

class Foo1
{
private:
    X x_;

public:
    void set_x(const X& value)
//             ^~~~~  ^
    {
        x_ = value;
    }

    const X& get_x()
//  ^~~~~  ^
    {
        return x_;
    }
};

Still there is a problem with the above solution. Since get_x does not modify the object it should be marked const. This is part of a C++ principle called const correctness.

The above solution will not let you get the property from a const object:

const Foo1 f;

X x = f.get_x(); // Compiler error, but it should be possible

This is because get_x not being a const method cannot be called on a const object. The rational for this is that a non-const method can modify the object, thus it is illegal to call it on a const object.

So we make the necessary adjustments:

class Foo2
{
private:
    X x_;

public:
    void set_x(const X& value)
    {
        x_ = value;
    }

    const X& get_x() const
//                   ^~~~~
    {
        return x_;
    }
};

The above variant is correct. However in C++ there is another way of writting it that is more C++ ish and less Java ish.

There are two things to consider:

  • we can return a reference to the data member and if we modify that reference we actually modify the data member itself. We can use this to write our setter.
  • in C++ methods can be overloaded by consteness alone.

So with the above knowledge we can write our final elegant C++ version:

Final version

class Foo
{
private:
    X x_;

public:
    X&       x()        { return x_; }
    const X& x() const  { return x_; }
};

As a personal preference I use the new trailing return function style. (e.g. instead of int foo() I write auto foo() -> int.

class Foo
{
private:
    X x_;

public:
    auto x()       -> X&       { return x_; }
    auto x() const -> const X& { return x_; }
};

And now we change the calling syntax from:

Foo2 f;
X x1;

f.set_x(x1);
X x2 = f.get_x();

to:

Foo f;
X x1;

f.x() = x1;
X x2 = f.x();
const Foo cf;
X x1;

//cf.x() = x1; // error as expected. We cannot modify a const object
X x2 = cf.x();

Beyond the final version

For performance reasons we can go a step further and overload on && and return an rvalue reference to x_, thus allowing moving from it if needed.

class Foo
{
private:
    X x_;

public:
    auto x() const& -> const X& { return x_; }
    auto x() &      -> X&       { return x_; }
    auto x() &&     -> X&&      { return std::move(x_); }

};

Many thanks for the feedback received in comments and particularly to StorryTeller for his great suggestions on improving this post.