开发者

C++ - const members / return const int& vs return int

开发者 https://www.devze.com 2023-02-20 18:26 出处:网络
What do we mean by those C++ lines? 开发者_运维技巧Are there alternative ways to writing them?

What do we mean by those C++ lines? 开发者_运维技巧Are there alternative ways to writing them?

const int& a() const;
int getA() const;

Thanks.


These two are two possible signatures for a member function in a class that promises not to change the object itself. In the first case it will return a constant reference to an integer (possibly a member attribute), the reference being const means that the caller will not be able to use it to change the internal attribute. The second case it returns an integer by value.

There are slight differences in the semantics, but in most cases they will not be important, consider them as two functions to obtain a value. For a case where it will make a difference see this:

class test {
public:
   test() : m_value() {
      std::cout << &m_value << std::endl; // print where the attribute is
   }
   int const & getValue() const {
      return m_value;
   }
   int copyValue() const {
      return m_value;
   }
   void setValue( int value ) {
      m_value = value;
   }
private:
   int m_value;
};
int main() {
   test t;                      // will printout an address [1]
   int v1 = t.getValue();       // caller copies the value
   int v2 = t.copyValue();      // caller copies the value (itself a copy in hte calle)
   int const &r = t.getValue(); // reference to t.m_value
   int const &c = t.copyValue();// reference to *copy* [2]
   std::cout << v1 << v2 << r << c
      << std::cout;             // 0000
   std::cout << &v1 << &v2      // 4 pointers, the third is [1] a r *is* t.m_value
      << &r << &c << std::cout; //     the rest should be different
   t.setValue( 5 );
   std::cout << v1 << v2 << r   // 0050, v1 and v2 where copies, r *is* t.m_value
      << c << std::cout;
}

The line marked with [2] uses a strange feature of the language by which if you obtain a constant reference to an r-value (temporary) the compiler will bind that temporary to the reference and keep it alive until the reference goes out of scope (basically it turns the r-value temporary into a hidden variable and binds the reference to it).

I added that line to be explicit in that the difference in behavior is not due to the receiving end main making a copy of maintaining a reference, but rather (also to be precise) by the signature of the accessor.


Both are equivalent ways to achieve the same thing:

const int& a() const;
int getA() const;

You are returning a value. The const at the right of the method header is a remark that functions getA() and a() won't modify the object that will execute them (the hidden this parameter). This is important at compile time, since it means that extra verifications will be taken into account while compiling. At runtime, there is no difference between those function above and these:

const int& a();
int getA();

However, the huge benefits of extending the verification capabilities of the compiler (nothing is being changed when you don't expect it to happen) are obviously worth the extra const.

The second part to worry about are the return types for both functions, which stands for the main difference between them, and probably are the motivation of the question. Let's change the subject to a different function:

std::string getName() const;
   { return name; }

Here, the return value will probably be the name attribute from the class. The return is being made by value, which means that a copy of the attribute will be created when returning from this method. This can be an issue when the string is big and you are moving a lot of strings by value around your application. Then it happens to appear the return-by-reference mechanism, which promises no copying:

std::string &getName() const
   { return name; }

This is actually very interesting: we are returning a reference, instead of a copy of the object. A reference is similar to a pointer, so you'll have to copy only a pointer (4 bytes in a 32 bit system) instead of the whole object. This is promising. However, it won't even compile. The compile will complain that you are returning a reference while you promised the method to be const, and therefore, the object it will execute over shouldn't be modified. This code would allow illegal operations to happen:

Person p( "Baltasar" );
p.getName() = "José";

cout << p.getName() << endl;

That's why the const for the return type appears as a new attractive, option that will solve the problem. A constant reference won't allow modifications of the object it points to, with:

const std::string &getName() const
    { return name; }

It will now compile, while the previous, malign code won't. Now let's return to our problem:

const int &getA() const;
int a() const;

The second one is a return-by-value, which means that the int (4 bytes) will be copied on return. The first one means that a constant reference to an int (4 bytes) will be returned. As it can be seen, there is no performance benefit in this case for using return-by-reference instead of return-by-value.

As a rule of thumb, return by const reference is always safe, it will never be more expensive than return by value.


const int& a() const;

a() returns a const reference to an int. const modifier at the end signifies that it cannot change the state of the object it is called up on.

int getA() const;

Same as the above description except that the return type is int which involves a copy of the returned variable if collected.

What is meant when said cannot change the state of object?

class foo
{
    int m_Var ;
    public:
        foo(int arg1) : m_Var(arg1){}
        void mutableMethod()
        {
            m_Var = 20 ;  // "this" has the variable m_Var and
                          //  by assigning it a value changes the state of
                          //  m_Var. Changing the state of it's member variable
                          //  is meant changing the state of object.
        }

        void nonMutableMethod() const
        {
             m_Var = 20 ;  //  This assignment is not allowed because of const 
                           //  modifier. The method is not allowed to change the
                           //  the state of object on which it is called ( this )
        }
};

Also, constant methods cannot return member variables by non-const reference.


The crucial difference is that:

  • getA() returns an int data value which can then be used by the caller completely independent of any other part of the program
  • a() returns a reference to some int that a() selects:
    • int x = a() "samples" the int value at that time, and is logically equivalent to int x = getA()
    • const int& x = a() saves a reference to the variable returned by a()!

Saving references doesn't always do what you expect or want

  • the GOOD: the compiler is smart enough to make a copy of that variable if it was a temporary / literal (e.g. const int& x = a(), const int& a() { return 3; })

  • the GOOD or BAD? (depending on whether it makes sense in the app): each time the value of x is read later, the program may (attempt to) reread it from the original int variable that a() internally returned: if that variable's value has since changed, then the value of x will also change. ("may" because the optimiser can avoid this when the value would be the same anyway)

  • the UGLY: if the memory at that address is no longer storing that variable (e.g. it was in newed memory that's since been deleted), then trying to read the value of x may result in an unpredictable value or crash the application (if the memory address is no longer readable).

Both a() and getA() are member functions of a class; we know this because only member functions can be const, which technically indicates they can't change non-mutable data members without casting away their constness, but the intent behind that restriction is that they shouldn't modify the caller-observable value of an object; mutable data is normally for caching, debug trace etc..


We can cast returned reference to pointer, so theoretically it makes more info available (address and value) than returning a copy (value).

And it's possible to hack const ref to mutable with const_cast.

Compiler will try to use value from original register, address or literal anyway.

Alternative ways for which goal? To make sure that staying constantly correct wouldn't add extra work. For const reference and const I find CR & CN macros handy.

#define CN  const
#define CR  const&         // Constant reference
#define CDa const*         // mutable pointer to constant data
#define CPD const * const  // constant pointer to constant data
const int&  verbose() const;
int CR      shorter() CN;

Side-effect is that declarations get shorter and as lines get shorter, the number of lines also decreases. Matter of taste tho... But when combined with DMAP macro, it seems to have an advantage.

typedef std::map<size_t, float>     TypeMap_Of_size_t_vs_float;
TypeMap_Of_size_t_vs_float          m_Map;
const TypeMap_Of_size_t_vs_float&   verboseIsNice() const
{
    return m_MyMap;
}
for each (auto myElement in verboseIsNice()) 
{
    myElement.foo();
}

vs

DMAP(SZ, Flo)  m_Map;                    // typedefs MSZFlo=std::map<size_t, float>
MSZFlo CR      tldr()                 CN { return m_Map; }
fe(el, tldr()) el.foo();

Without auto and with using iterators the example would show more than 333% of difference.

0

精彩评论

暂无评论...
验证码 换一张
取 消