Having a public copy constructor will make the little program compile, but not showing the side effect "Copy".
#include <iostream>
class X
{
public:
X(int) { std::cout << "Construct" << std::endl; }
// Having a public copy constructor will make the little program
// compile, but not showing the side effect "Copy".
private:
X(const X&) { std::cout << "Copy" << std::endl; }
private:
X& operator = (const X&);
};
int main() {
X x = 1;
return 0;
}
Here are the relevant bits of the C++ standard that are involved:
In other words, it doesn't matter if a compiler optimization could elide the copy, the initialization is ill-formed because there are no applicable constructors. Of course, once you make the copy constuctor public, the following section applies:
You initialisation is being optimised by the compiler down to:
This is a kind of copy elision, and is allowed by the standard, even though it can remove side effects like you saw.
From the C++03 standard section 12.8:
The second case is what we have here.
You have used so-called "copy initialization" (defined in
[decl.init]
). The defined meaning is to construct a temporary object of typeX
using theint
constructor, and then initializex
from the temporary using the copy constructor.However, the standard also permits an optimization called "copy constructor elision" (defined in
[class.copy]
) in this case. If that optimization is applied there is no temporary.x
is constructed using theint
constructor just as if you'd written so-called "direct initialization"X x(1);
.In order that you don't accidentally write code that compiles when the optimization is applied but not when it isn't, the standard requires that the copy constructor must be accessible even if it is elided. Hence, the constructor must be public even though (with the compiler and options you're using) it's not called.
In C++11 move constructors are considered, and are eligible for elision too. However this class
X
doesn't have a move constructor, so C++11 and C++03 are the same for this example.My best guess is that this is a compiler optimization. There is a valid path to declaring an object this way if you have a copy constructor. Consider doing this explicitly:
Or more explicitly:
So instead of using
operator=
, it knows you are trying to initialize your object since the declaration. In essence, the compiler is being "smart". Finally, it optimizes this again to this step:Therefore, after the compiler figures out "what you were trying to do" this becomes a standard initialization of the object.
EDIT
For completeness, notice that if you try this:
You will see the issue with private
operator=
. Explicitly, this is important to notice thatoperator=
is never actually used in the original initialization question above even though=
appears in the code.