C++ – How to write C++ getters and setters

cc++-faqgetter-setter

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.

Best Answer

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.