Вопрос

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?

Это было полезно?

Решение

There are a few reasons why (inline) functions that return an appropriate reference are better:

  1. A reference will require memory (typically the same amount as a pointer) in each object

  2. 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

  3. Initializing a reference requires (a minuscule amount of) time

  4. Having a member field of reference type will disable the default copy and move assignment operators since references are not reseatable

  5. 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

  6. 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.

The first option requires extra memory, just like a pointer.

If you do:

inline const T& value() const{
      return m_value;
}

you have the same benefit than the first approach without the need of extra memory.

Also, since the first approach needs C++11, it will be less likely that people use it.

General use for returning const references:
Returning a constant reference is common for cases where creating or destroying a copy is expensive.
In general, the rule is: If passing and using a reference is more expensive than making a copy, don't do so. If you were not asked for a subobject, it is normally even impossible.
Otherwise, it's a valid performance-optimization to return constant references, which is transparent to well-behaved source-code.
Much template code returns constant references even where the above test does not indicate, just to treat all specialisations equally and because the function is really small and is nearly guaranteed to be inlined anyway.

Now, to the meat of your curious find (never saw its like yet):
+ No need for an accessor function (still, this is lame, it will be compiled out anyway)
- The object is bigger (references normally need as much space as pointers)
- Potentially need better alignment due to the above point.
- Has no magic member functions, because the compiler does not know how to copy references
- For debug builds, one cannot add additional checks.

The same look&feel without that collateral damage can be achieved like this btw (only additional checks for debug stay impossible):

template<typename T>
struct example {
    example(T t): value{t}{}
    union{
        const T value;
        struct{
        private:
            T value;
            friend class example<T>;
        } _value;
    };
};
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top