Reputation: 3460
I'm trying to compile the following simple code with GCC 4.7.2 (MinGW). Here I'm using C++11 feature - non-static member initializers:
#include <iostream>
using namespace std;
struct A
{
int var;
A()
{
cout << "A()\n";
}
A(int i)
{
cout << "A(int i)\n";
var = i;
}
A(const A&) = delete;
};
struct B
{
A a = 7;
};
int main()
{
B b;
cout << "b.a.var = " << b.a.var;
return 0;
}
This code doesn't compile because of deleted copy-constructor which isn't necessary here. Here are errors:
main.cpp:27:11: error: use of deleted function 'A::A(const A&)'
main.cpp:13:5: error: declared here
main.cpp: In constructor 'constexpr B::B()':
main.cpp:25:8: error: use of deleted function 'A::A(const A&)'
main.cpp:13:5: error: declared here
If I implement copy-constructor like this:
A(const A& a)
{
cout << "A(const A&)\n";
var = a.var;
}
Then code compiles fine and program gives me expected output:
A(int i)
b.a.var = 7
So it means that copy constructor is not used, but why I can't delete it?
Edit: Thanks for your answers. Copy or move constructor is required by standard if I'm using =
. To fix this problem I need to implement move constructor or use direct initialization syntax A a{7}
.
Upvotes: 3
Views: 1043
Reputation: 126522
Per Paragraph 12.2/14 of the C++11 Standard:
The initialization that occurs in the form
T x = a;
as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization. [ Note: Copy-initialization may invoke a move (12.8). —end note ]
The reason why your copy-initialization doesn't compile is that during copy-initialization a temporary object needs to be created (at least logically), and the object being initialized shall be constructed from it.
Now all the previous answers seem to focus just on copy-constructors, but the first problem here is the absence of a move-constructor. As long as you provide one, then it is true that the copy constructor is not necessary.
Alas, deleting the copy constructor prevents the generation of an implicit move constructor. Adding one explicitly would fix the problem:
struct A
{
int var;
A()
{
cout << "A()\n";
}
A(int i)
{
cout << "A(int i)\n";
var = i;
}
A(const A&) = delete;
// THIS MAKES IT WORK
A(A&& a)
{
cout << "A(A&&)\n`;
var = a.var;
}
};
Notice that when both the move-constructor and the copy-constructor are present, the move-constructor is preferred, because the temporary created for copy-initializing your object is an rvalue.
When a move-constructor is absent, the compiler can invoke the copy constructor to perform the initialization, because constant lvalue references can bind to rvalue references and copy is seen as an unoptimized move.
However, even though the compiler is allowed to elide the call to the move or the copy constructor, the semantics of the operation must still be checked. Per Paragraph 12.8/32 of the C++11 Standard:
When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ] [...]
Therefore, an error is issued by the compiler if neither the move constructor nor the copy constructor are present.
If you want, however, you can direct-initialize your object rather than copy-initializing it. Just use the direct-initialization syntax instead:
struct B
{
A a{7};
};
This will make the move-constructor and copy-constructor unnecessary, because no temporary is created when direct-initializing an object.
Upvotes: 2
Reputation: 110738
The initialiser for a
gives you copy-initialization:
A a = 7;
For such a copy-initialization, where a user-defined conversion is required, the resulting initialisation is equivalent to:
A a(A(7));
That is, a temporary A
is constructed and then passed to the copy constructor of your a
object. This copying may be elided, but the copy constructor must be available nonetheless. In other words, the copying can only be elided if the copy would be possible in the first place. If you delete
the copy constructor, the copying is impossible.
You'll have a better time with your deleted copy constructor if you do the following:
A a{7};
This does direct-initialization and no copy constructor is required.
Upvotes: 4
Reputation: 361682
So it means that copy constructor is not used, but whyI can't delete it?
In your case, copy constructor is used only for semantic-check as required by the Standard, it also needs to be accessible. Later on, the compiler optimizes the code, eliding the call to the copy-constructor, so it is not actually invoked.
Upvotes: 1
Reputation: 385295
This code doesn't compiles because of deleted copy-constructor which isn't necessary here
Sorry, but your copy constructor is necessary. Even though the copy can be optimised out, it must still be possible in the code. This mandated by the language.
Upvotes: 1
Reputation: 393613
Copy initialization is allowed to elide the copy but the copy constructor is mandated to be accessible by the standard.
Upvotes: 3