I was just testing looking through some code and noticed something similar to:
template<typename T> class example{ public: example(T t): m_value{t}{} const T &value = m_value; private: T m_value; };
I haven't seen this before. Almost every API or library I've used before defines a function that returns a member variable like so, and not a constant reference to it:
template<typename T> class example{ public: example(T t): m_value{t}{} const T &value() const{ return m_value; } private: T m_value; };
Why is the first way less common? What are the disadvantages?
One way to look at this is this: If a function manipulates an object's inner state, then that's a good indication that this function should probably be a member function. If a function uses an object without changing its inner state, then that's a good indication that this function probably should be a free function.
The call by reference method of passing arguments to a function copies the reference of an argument into the formal parameter. Inside the function, the reference is used to access the actual argument used in the call. This means that changes made to the parameter affect the passed argument.
Pass-by-reference means to pass the reference of an argument in the calling function to the corresponding formal parameter of the called function. The called function can modify the value of the argument by using its reference passed in.
If you recall, using pass by reference allows us to effectively “pass” the reference of a variable in the calling function to whatever is in the function being called. The called function gets the ability to modify the value of the argument by passing in its reference.
There are a few reasons why (inline) functions that return an appropriate reference are better:
A reference will require memory (typically the same amount as a pointer) in each object
References typically have the same alignment as pointers, thus causing the surrounding object to potentially require a higher alignment and thus wasting even more memory
Initializing a reference requires (a minuscule amount of) time
Having a member field of reference type will disable the default copy and move assignment operators since references are not reseatable
Having a member field of reference type will cause the automatically generated default copy and move constructors to be incorrect, since they will now contain references to the members of other objects
Functions can do additional checking like verifying invariants in debug builds
Be aware that due to inlining, the function will usually not incur any extra costs beyond a potentially slightly larger binary.
Encapsulation.
For Object-Oriented Programming purists, m_value
is an implementation detail. Consumers of class example
should be able to use a single reliable interface to access value()
and shouldn't be dependent on how example
happens to determine it. A future version of example
(or an complicated template specialization) might want to use caching or logging before returning a value()
; or it might need to calculate value()
on the fly because of memory constraints.
If you don't use an accessor function originally, everything that uses example
might have to change if you change your mind later. And that can introduce all kinds of waste and bugs. It's easier to just abstract it one level further by providing an accessor like value()
.
On the other hand, some people just aren't as rigid about those kinds of OOP principles and just like to write code that's efficient and legible, dealing with refactoring when it happens.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With