3

I have a simple snippet:

class Object
{
private:
    int value;
public:
    Object(int value) : value(value) { cout << "Object::ctor\n"; }
    Object(const Object& obj) { cout << "Object::copy-ctor\n"; }
    Object(Object&& obj) { cout << "Object::move-ctor\n"; }

};

Object take_and_return_obj(Object o) { return o; }

int main()
{
    Object o(5);
    take_and_return_obj(o);
}

Now, this, as expected, prints a copy and move constructor.

Object::copy-ctor
Object::move-ctor

This is because o gets copied into the function using the copy-ctor, and then gets sent back using the move-ctor since the function is over and the return value is an xvalue.

However, something happens when the initial argument to the function is also an xvalue:

int main()
{
    Object o = take_and_return_obj(Object(5));
}

What happens is that somehow nothing happens when the value is sent to the function:

Object::ctor
Object::move-ctor

I assume that the move is for the return operation, so that is not affected by this change. However there is no copy-ctor called to create the o inside the function's scope. I know its not any kind of pointer or reference since I made the function take the argument by value.

So my question is: what exactly happens to the xvalue I create in main so that the argument inside the function gets its value?

This is more of an educational question, so do not be afraid to go into more in-depth answers.

Enlico
  • 23,259
  • 6
  • 48
  • 102
Rares Dima
  • 1,575
  • 1
  • 15
  • 38
  • 1
    More likely, the ctor and move-ctor are used in the call, and [copy elison](https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization) is used when returning the value. – Bill Lynch Oct 04 '21 at 06:33
  • 1
    OT, you should correct the first code, as it doesn't work: the class should have a default constructor `Object()` or, alternatively, `main` shouldn't default construct `o`. – Enlico Oct 04 '21 at 06:36
  • 3
    @BillLynch this is not the case. If you place a `cout` before the `return` inside the function you can see that the `move` will happen after the `cout`, proving that it has to do with the return. Maybe copy-elision still uses move semantics if they are available? – Rares Dima Oct 04 '21 at 06:43
  • 2
    As regards the doubt on where the move is happening, function parameters _are not eligible for copy elision with resepct to their function's return value, but compilers must treat them as rvalues if they're returned_ (Effective Modern C++ by Scott Meyers, page 176). So it's true that the move is happening on the return statement, not the copy elision/RVO. – Enlico Oct 04 '21 at 06:54

2 Answers2

5

Yes, in take_and_return_obj(Object(5));, the copy/move operation for constructing parameter o is elided; which is guaranteed since C++17.

Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible:

  • ...

  • ... In the initialization of an object, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type:

    T x = T(T(f())); // only one call to default constructor of T, to initialize x
    
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
0

This answer is for those who do not know the term copy-elision.

The question is answered by this answer (including the 2 linked answers).

In certain cases the compiler may omit copying things. Such cases include scenarios like taking or returning xvalues from functions. In these cases the copy/move ctors will not be called so that no extra copies of the object are created.

Rares Dima
  • 1,575
  • 1
  • 15
  • 38