c++: MyClass x(1,2,3) vs MyClass x = MyClass(1,2,3)

Viewed 114

What is the difference between MyClass x(1,2,3); and MyClass x = MyClass(1,2,3); in c++?

If MyClass(1,2,3) works on its own, then can I write:

doSomething(MyClass(1,2,3));

instead of

MyClass x(1,2,3);
doSomething(x);
1 Answers

C++17

Due to mandatory copy elison from C++17, both(MyClass x(1,2,3) and MyClass x = MyClass(1,2,3))of them are the same.

This can be seen from the following quoted statement:

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:

This means that in MyClass x = MyClass(1,2,3); there can be no call to the copy/move constructor and the object x will be created as if you wrote MyClass x(1,2,3);.


Prior C++17

Prior to C++17, the second MyClass x = MyClass(1,2,3); creates a temporary object and then copy/moves that temporary using the copy/move constructor. Note that prior to C++17, the compiler has the option to elide this copy/move operation.


Now what happens when doSomething is called depends on the parameter type of that function. Below we consider different parameter types:

Case 1

Suppose we have:

void doSomething(MyClass m)
{
    
}
doSomething(MyClass(1,2,3)); //no call to copy/move ctor can happen from c++17 

Here from C++17, when passing MyClass(1,2,3) there can be no call to the copy/move constructor due to mandatory copy elison. But prior to C++17, there can be a call to the copy/move constructor. There is also a possibility that copy/move operation is elided.

Case 2

Suppose doSomething is as follows:

//----------------------------v----->note the lvalue reference
void doSomething(const MyClass& m)
{
    std::cout<<"doSomething"<<std::endl;
}
doSomething(MyClass(1,2,3));

Here there will be temporary materialization and m will refer(bound) to that. Note there will be no call to copy/move constructor in this case in both C++11 and C++17.

Case 3

Suppose doSomething is as follows:

//----------------------vv---------->note &&
void doSomething(MyClass&& m)
{
     std::cout<<"doSomething"<<std::endl;
}
doSomething(MyClass(1,2,3));

Here also there will be temporary materialization and m will refer(bound) to that. Note there will be no call to copy/move constructor in this case in both C++11 and C++17.


Now we will consider what happens if you pass x to doSomething for different parameter type of doSomething.

Case 4

Suppose we have:

void doSomething(MyClass m)
{

}
MyClass x(1,2,3);
doSomething(x); //calls the copy constructor in both c++17 and prior to c++17

Here we're passing x by value to the function doSomething and this will be done using the copy constructor of class MyClass as it involves copy initialization in both C++17 and prior to C++17.

Case 5

Suppose we have:

//----------------------------v------>note lvalue reference
void doSomething(const MyClass& m)
{

}
MyClass x(1,2,3);
doSomething(x); //the parameter `m` will be bound to the passed argument x in both c++17 and prior to c++17

Here the parameter m will be bound to the passed argument x in both c++17 and prior to c++17. No call to the copy constructor will be made.

Case 6

Suppose we have:

//----------------------vv-------->note &&
void doSomething(MyClass&& m)
{

}
MyClass x(1,2,3);
doSomething(x);  //WON'T COMPILE as we can't bind an lvalue to an rvalue reference
Related