4

Quite often you need to define a getter returning a reference to a member, and a corresponding setter.

They usually look somewhat like this :

class Foo
{
    Type member;
public:
    const Type& getVar() const { return member; }
    void setVar(const Type & value) { member = value; }
}

However, sometimes you have to modify the Type object returned by getVar and need to get a non-const reference to it, so it is required to add another getter.

How do you call this additional method? I have never seen a "standard" way to write it. Is there a way to write it so that when anyone read the name, they understand it is a non-const getter ?

What I currently do is this :

Type & retrieveVar() { return member; }

I'm not sure it is explicit enough for the reader. How do you deal with this issue ?

  • 2
    The const and non-const getters can have the same name. Is there a particular reason why you need different names? I can think of such reasons (like if retrieving a non-const reference would be more costly, e.g. due to copy-on-write shared resources so you never want that to happen implicitly), but that's far from typical. – amon Jul 3 '18 at 19:04
  • If they have different names, someone reading the code and encountering the getter would know at first glance if we intend to modify the data or not. Maybe you are right and it is not really a concern, people usually deduce that from the context and what we actually do with the reference. – Norgannon Jul 3 '18 at 20:27
  • 1
    Well, by itself the getter doesn't modify anything – if it does, calling it a getter might be misleading. Then the question is how the returned value will be used. If I only call const methods on it, it doesn't matter if I got a const or non-const reference. There are of course some cases where the const version would return a different type, e.g. consider std::vector::begin() vs. std::vector::cbegin(). – amon Jul 3 '18 at 20:35
  • 1
    @amon sometimes would return a different type. Now we have std::as_const, we can drop those – Caleth Jul 3 '18 at 20:39
  • 1
    if you need get/set/modify for a class member, you should reconsider your architecture; that is often a code-smell. Whatever you want to do with/to that member variable should happen inside the class, by calling a method 'do_something' on the object. – Aganju Jul 4 '18 at 2:09
3

As far as the C++ standard library is concerned, there are two distinct cases, based on the answer to one question: are you accessing an object or manipulating an object's property?

Accessing the contents of a container is accessing a conceptual subobject of the container. This is usually done via an overloaded function that returns a reference to the object. That reference will be const or non-const based on whether the container is const or not. This is true whether you're talking about std::get<tuple>, std::optional::value, or std::vector::at.

For manipulating properties of an object, this is typically done via return values/parameters. It's still using an overloaded function; one overload takes no parameters at all, while the other overload takes the value to set the property to. The latter is not const, and in many cases both will return the (pre-modification) value.

Iterators are an interesting intersection. Most iterators return references to objects in their containers. But the Input/OutputIterator distinction makes it possible for input iterators to generate values that aren't part of some container. Their operator* could return a value rather than a reference to an object.

Similarly, output iterators often have their operator* return a proxy-object which behaves like a reference. This proxy uses the assignment operator to set the value into the output stream, without having to return an actual value of that type.

But in virtually all cases in the standard library, you have one function name with multiple overloads based on the const-ness of the source object.

The most prominent case of the standard breaking this rule is atomic::load/store, which are independently named functions. One could have made them the same function, but being able to visually distinguish atomic load operations from stores is pretty important, since they can adversely affect the functionality of your code.

That being said, C++ as a whole has no guidelines on this. The above is just how the standard library handles it in most cases. Other libraries will frequently use get/set. Some libraries will call out get but not setting functions.

5

You don't need two method names, let alone three. This is the main style used by classes in std:

class Thing
{
          Type & var();
    const Type & var() const;
}

See for example std::vector::front, std::get, any iterator's operator*, every at or operator[] I can find.

There are also instances of by-value passing

class Thing
{
    Type var() const;
    void var(Type);
}

See for example std::basic_ios::exceptions.

Very often only the "getter" will be present, which makes it hard to determine what is a "property" and what is a calculation.

The closest I have found to a "classic" get_x set_x "property" are global callbacks, for global error handling / recovery.

See std::set_new_handler, std::get_new_handler

  • If that's really the style, please name two examples following it. – Deduplicator Jul 4 '18 at 14:16
  • @Deduplicator: std::get and std::vector/array::at. Oh, and std::vector/array::operator[]. And operator* for every iterator type. And those are just the ones I came up with off the top of my head. – Nicol Bolas Jul 4 '18 at 14:22
  • 1
    @Deduplicator: "Those aren't actually getters / setters for singular properties of the object." It is for std::tuple<Ts>. And for std::variant<Ts>. And for std::optional<T>. And for std::any. Each of those things holds a fixed set of properties defined at compile-time. And each of them uses a single overloaded function for non-modifying/modifying access, rather than a get/set pair. This is the C++ standard library's common convention for this. Other libraries have their own conventions, but you cannot deny that this is how C++'s standard library handles it. – Nicol Bolas Jul 4 '18 at 15:42
  • 1
    @Deduplicator It's also quite a stretch to say that the object returned by operator* is not "[a] singular property of the [iterator or pointer object]", even if it isn't owned or contained within the storage of that object – Caleth Jul 4 '18 at 15:44
  • 1
    @Deduplicator: "And std::get is not a member..." It's C++; functions don't have to be members to be part of a type's interface. As for invariants, what does it matter? The OP asked about accessors for member subobjects, not for establishing invariants. – Nicol Bolas Jul 4 '18 at 17:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.