C++ copy-construct construct-and-assign question
Asked Answered
S

3

9

Here is an extract from item 56 of the book "C++ Gotchas":

It's not uncommon to see a simple initialization of a Y object written any of three different ways, as if they were equivalent.

Y a( 1066 ); 
Y b = Y(1066);
Y c = 1066;

In point of fact, all three of these initializations will probably result in the same object code being generated, but they're not equivalent. The initialization of a is known as a direct initialization, and it does precisely what one might expect. The initialization is accomplished through a direct invocation of Y::Y(int).

The initializations of b and c are more complex. In fact, they're too complex. These are both copy initializations. In the case of the initialization of b, we're requesting the creation of an anonymous temporary of type Y, initialized with the value 1066. We then use this anonymous temporary as a parameter to the copy constructor for class Y to initialize b. Finally, we call the destructor for the anonymous temporary.

To test this, I did a simple class with a data member (program attached at the end) and the results were surprising. It seems that for the case of c, the object was constructed by the copy constructor rather than as suggested in the book.

Does anybody know if the language standard has changed or is this simply an optimisation feature of the compiler? I was using Visual Studio 2008.

Code sample:

#include <iostream>

class Widget
{
    std::string name;
public:
    // Constructor
    Widget(std::string n) { name=n; std::cout << "Constructing Widget " << this->name << std::endl; }
    // Copy constructor
    Widget (const Widget& rhs) { std::cout << "Copy constructing Widget from " << rhs.name << std::endl; }
    // Assignment operator
    Widget& operator=(const Widget& rhs) { std::cout << "Assigning Widget from " << rhs.name << " to " << this->name << std::endl; return *this; }
};

int main(void)
{
    // construct
    Widget a("a");
    // copy construct
    Widget b(a);
    // construct and assign
    Widget c("c"); 
    c = a;
    // copy construct!
    Widget d = a;
    // construct!
    Widget e = "e";
    // construct and assign
    Widget f = Widget("f");

    return 0;
}

Output:

Constructing Widget a

Copy constructing Widget from a

Constructing Widget c
Assigning Widget from a to c

Copy constructing Widget from a

Constructing Widget e

Constructing Widget f
Copy constructing Widget from f

I was most surprised by the results of constructing d and e. To be precise, I was expecting an empty object to be created, and then an object to be created and assigned to the empty object. In practice, the objects were created by the copy constructor.

Snapp answered 17/3, 2010 at 14:0 Comment(3)
"In the case of the initialization of b, we're requesting the creation of an anonymous temporary of type Y, initialized with the value 1066"... and the same in the initialization of c, it's just harder to see the temporary.Quincyquindecagon
Note that Y c = 1006 is not possible if your constructor is declared as explicit... as any one parameter constructor should, most of the time.Damek
Matthieu, yes I agree with what you said and I always do that when programming. Your point is also covered in the book "More effective C++" if I remember correctly.Snapp
W
17

The syntax

X a = b;

where a and b are of type X has always meant copy construction. Whatever variants, such as:

X a = X();

are used, there is no assignment going on, and never has been. Construct and assign would be something like:

X a;
a = X();
Whew answered 17/3, 2010 at 14:5 Comment(1)
Thanks for the comment about copy construction. I was not really aware of that - I always thought that you have to do X=a(b); to be sure of invoking the copy constructor.Snapp
B
6

The compiler is permitted to optimize cases b and c to be the same as a. Further, copy construction and assignment operator calls can be wholly eliminated by the compiler anyway, so whatever you see isn't necessarily the same with different compilers or even compiler settings.

Brisesoleil answered 17/3, 2010 at 14:3 Comment(5)
So this means the book may not be 100% correct now? Interesting.Snapp
@Andy, no the book is 100% correct. But it's not 100% complete. If you want 100% completion, you need to consult the Standard. Specifically, it (or the part you quoted) does not explain that the compiler is allowed to optimize copies if copying is done from a temporary like above.Fez
Thank you. I guess you should still use the formats suggested by the book, in case that the compiler you are using does not implement the optimisation.Snapp
No you should not. Use whichever is clearer and don't worry about those minor optimizations.Damek
@Johannes: "The book says the truth, and nothing but the truth, but not the whole truth." :)Brisesoleil
H
3

As of C++17, all three of these are equivalent (unless Y::Y(int) is explicit, which would simply disallow c) because of what is often called mandatory copy elision.

Even Y c = 1066; creates only the one Y object because the result of the implicit conversion to Y is a prvalue that is used to initialize c rather than to create a temporary.

Hearttoheart answered 15/9, 2017 at 3:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.