简体   繁体   中英

returning rvalue? Or : Why does copy constructor get called on `return <expression>`?

I am suspecting I vaguely know the reason for what I am observing, but I would like confirmation or correction and some explanation to it.

I have the following code:

template <class T>
class C
{

public:

    C () = default;

    C(const C& rhs) : mem(rhs.mem)
    {
        std::cerr << "copy" << "\n";
    }

    // does not call copy constructor twice
    // friend C operator<<(C& src, unsigned shift)
    // {
    //     std::cerr << 1 << "\n";
    //     C tmp(src);
    //     std::cerr << 2 << "\n";
    //     tmp <<= shift;
    //     std::cerr << 5 << "\n";
    //     return tmp;
    // }

    // does call copy constructor twice
    friend C operator<<(C& src, unsigned shift)
    {
        std::cerr << 1 << "\n";
        C tmp(src);
        std::cerr << 2 << "\n";
        return (tmp <<= shift);
    }

    friend C& operator<<=(C& src, unsigned shift)
    {
        std::cerr << 3 << "\n";
        src.mem <<= shift;
        std::cerr << 4 << "\n";
        return src;
    }

    T mem;
};

int main()
{
    C<int> c1;

    c1 << 3;
}

I have two versions of C<T>::operator<<(C<T>, unsigned)

the difference is the one returns the result of an expression:

return (tmp <<= shift);

the other returns a variable:

return tmp

So far I thought the two functions would be semantically identical and the one with return (tmp <<= shift); simply be better style, like return a + 1 would be better style than int ret = a + 1; return ret int ret = a + 1; return ret . This is apparently not so and probably only holds true for atomic data types. The output of the version with return (tmp <<= shift); looks like this:

1
copy
2
3
4
copy

The output of the other like this:

1
copy
2
3
4
5

Is my assumption right that infact return (tmp <<= shift); does not return tmp after invoking <<= on itm but rather create a new object as a copy of tmp after <<= has been invoked?

When returning by value, the return value must be initialized. The return value is an object of type C here.

In the case return (tmp <<= shift); , it means that (tmp << shift) is the initializer for a C . Since that is an lvalue of type C , this is a copy-construction.

There is a special rule that for a return statement of the form return identifier; , then it can be move-construction even though identifier is an lvalue. But that rule doesn't extend to other expressions (yet).

The other version of your code does activate the special rule:

tmp <<= shift;
return tmp;

Here tmp may be treated as an rvalue, making it movable (and also this makes it a copy elision context).

In your testing your compiler implemented copy-elision. To test without copy-elision (if your compiler supports letting the user configure that) you'd need to also give your C a move-constructor. A user-provided copy-constructor suppresses implicit generation of a move-constructor.

the operator <<= may not return the reference to the input parameter (maybe it will return reference to another static / global variable), so the compiler can not easily decide to use return value optimization , and need to invoke the copy constructor.

In your question: When you write return (tmp <<= shift) the compiler doesn't know whether or not (tmp <<= shift) will return reference to tmp, but if you write return tmp the compiler knows it and can optimize it out.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM