5

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);
Joe C.
  • 397
  • 4
  • 15
  • 1
    From C++17 they're same(equivalent). – Jason May 22 '22 at 16:24
  • Pre-C++17 the former would create a second object, and move one into the other (unless that's optimized away). *"can I write ..."* Yep. – HolyBlackCat May 22 '22 at 16:26
  • 5
    You managed to ask about three unrelated things here without realizing it. The question at the beginning is not really relevant to the other code snippets you are showing. – user17732522 May 22 '22 at 16:30
  • @user17732522 the top 2 are at least kind of related, for example, if MyClass x = MyClass(1,2,3) is just syntactic sugar for the first statement, then that would mean myfunc(MyClass(1,2,3)) won't work. i.e. if MyClass(1,2,3) is meaningless on its own. The third part is just because I was worried its a stupid question – Joe C. May 22 '22 at 16:38
  • @JoeC. You could ask a separate follow up question for the 3rd or 2nd part of your question. The more concise the question is the more easier it will be for other user to answer it(since the user doesn't have to know the answer to all the questions). Additionally, if you ask a separate question you can specify additional information/details in that question that won't get mixed up in the same question. – Jason May 22 '22 at 16:40
  • The point is that (since C++17) the answer to the initial question is "There is no difference.". However `MyClass x(1,2,3); doSomething(x);` is not the same as `doSomething(MyClass(1,2,3));`. And `std::cin.rdbuf(std::istringstream(str).rdbuf());` doesn't work because of lifetime considerations specific to that scenario. I don't have time to write a full answer right now, but it seems the current answers do not really go into this. – user17732522 May 22 '22 at 16:41
  • @JoeC. Can you add what is the type of the parameter of `doSomething` in your example. Then we can answer accordingly. – Jason May 22 '22 at 16:46
  • @AnoopRana If there is no difference between `MyClass x(1,2,3);` and `MyClass x = MyClass(1,2,3);`, then I would expect the code snippets in the second part to always behave the same. If that's not true, could you elaborate? (in an answer) – Joe C. May 22 '22 at 16:52
  • 1
    @JoeC. Yes, you get the point. See in my updated answer i have explained what happens depends upon the parameter type of `doSomething`. I have considered and expalined for all three types of parameters. – Jason May 22 '22 at 17:06
  • @AnoopRana No, they are not the same. In `doSomething(x)` the class has a name, it can not be moved. You would have to write `doSomething(std::move(x))` to get be equivalent. – Goswin von Brederlow May 22 '22 at 17:52
  • @GoswinvonBrederlow When i said they are the same i was talking about `MyClass x(1,2,3);` and `MyClass x = MyClass(1,2,3);` as this is the title of the question. – Jason May 22 '22 at 17:55
  • @AnoopRana sorry. questions with too many questions are always confusing – Goswin von Brederlow May 22 '22 at 17:56
  • @JoeC. I have added some more cases(case 4,5 and 6 in particular), to explain what you asked. Check it out. – Jason May 22 '22 at 19:29

1 Answers1

2

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
Jason
  • 36,170
  • 5
  • 26
  • 60