C++ copy-construct construct-and-assign question

2019-01-12 07:17发布

问题:

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.

回答1:

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();


回答2:

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.



回答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.