Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 828
bolov Avatar asked Jul 31 '18 14:07

bolov


People also ask

What are getters and setters in C?

The getter function is used to retrieve the variable value and the setter function is used to set the variable value. Remember: You can directly access public member variables, but private member variables are not accessible. Therefore, we need getter functions.

What is the difference between getter and setter in C++?

A “getter” or “getter method,” on the other hand, is a method whose sole purpose is to return the current data of a class field. Getter and setter functions allow access to the private data in a safe mode. The getter function is also known as accessors whereas the C++ setter function is known as the mutators function.

What are getters and setters in PHP?

Getters and setters are fundamentals of the encapsulation principle. Getters give public access to private data. Setters allow for a private variable to be modified. Coding Champ Home (current) Tutorials PythonJavaJavaScriptC++C#PHP Getters And Setters Getters Gettersgive public access to private data.

What is the purpose of setters in getters?

At line 8a getter is defined and a default value is set for the class member side. If we uncomment line 20the code won't compile. That's because a setter is not defined. Setters Settersallow for a private variable to be modified. They are important since they can provide validation before a value is set.

Do getters and setters need to expose a data member?

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:


2 Answers

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.

like image 112
Caleth Avatar answered Oct 12 '22 02:10

Caleth


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.

like image 25
Jerry Coffin Avatar answered Oct 12 '22 04:10

Jerry Coffin