Reputation: 1
I have an uncopiable class. Copying this would be problematic. I want to guarantee that it won't be ever copied, so I made its copy constructor deleted
:
class A {
public:
A();
A(const A&) = delete;
};
A fun() {
return A();
};
int main() {
A a = fun();
};
Unfortunately, g++ won't compile this on the reason:
t.cc: In function ‘A fun()’:
t.cc:8:12: error: use of deleted function ‘A::A(const A&)’
return A();
^
t.cc:4:5: note: declared here
A(const A&) = delete;
^
t.cc: In function ‘int main()’:
t.cc:12:13: error: use of deleted function ‘A::A(const A&)’
A a = fun();
^
t.cc:4:5: note: declared here
A(const A&) = delete;
^
But this is a very clear situation where copy elision should be used, so the copy constructor shouldn't be ever called. Why is it so?
Upvotes: 27
Views: 5487
Reputation: 3637
OP's code is now valid since C++17. But GCC and clang will only accept it when the option std=c++17
is enabled.
Using the following minimum code as an example:
class Uncopyable
{
public:
// should never be copied by value
Uncopyable (const Uncopyable&) = delete;
Uncopyable& operator= (const Uncopyable&) = delete;
Uncopyable() {}
};
int main(void)
{
Uncopyable obj1;
auto obj2 = Uncopyable();
}
When compiled with std=c++14
, a compiler only accepts the declaration of obj1
, but rejects assignment to obj2
as invalid. GCC shows the following errors:
$ g++ copyassignment.cpp -Wall -Wextra -O2 -std=c++14 -pedantic
copyassignment.cpp: In function ‘int main()’:
copyassignment.cpp:14:32: error: use of deleted function ‘Uncopyable::Uncopyable(const Uncopyable&)’
14 | auto obj2 = Uncopyable();
| ^
copyassignment.cpp:5:9: note: declared here
5 | Uncopyable (const Uncopyable&) = delete;
| ^~~~~~~~~~
copyassignment.cpp:14:32: note: use ‘-fdiagnostics-all-candidates’ to display considered candidates
14 | auto obj2 = Uncopyable();
| ^
copyassignment.cpp:14:14: warning: unused variable ‘obj2’ [-Wunused-variable]
14 | auto obj2 = Uncopyable();
| ^~~~
When compiled with std=c++17
, compilers now accept both variables as legal, thanks to guaranteed copy elision:
copyassignment.cpp: In function ‘int main()’:
copyassignment.cpp:14:14: warning: variable ‘obj2’ set but not used [-Wunused-but-set-variable]
14 | auto obj2 = Uncopyable();
| ^~~~
Since copy elision is implicit, you can be relying on it without actually realizing the fact, and this incompatibility may come as a nasty surprise - as I accidentally discovered today. It shows the usefulness of deleted copy constructor as a compile-time assertion. Without deleting it, one may never notice this problem to begin with...
Upvotes: 1
Reputation: 31465
Until C++17 copy elision is an optimization the compiler is not required to do, so classes must be copyable since the compiler might want to copy (even if it actually does not). In C++17 copy elision will be guaranteed in many cases and then classes won't need copy ctors.
See also:
http://en.cppreference.com/w/cpp/language/copy_elision
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0135r0.html
https://herbsutter.com/2016/06/30/trip-report-summer-iso-c-standards-meeting-oulu/ (the bit about "Guaranteed copy elision")
You could perhaps use the old trick of declaring the copy constructor in your class but not actually implement it? That should please the compiler as long as it does not actually invoke the copy ctor. I didn't test that, but I believe it should work for your case until C++17 arrives.
Upvotes: 25
Reputation: 36483
You can't force copy elision (yet) (see other answers).
However, you can provide a default move constructor for your class, this will move (and thus, not copy) the return value if RVO/NRVO is not possible. To do this you should add = default
for your move constructors:
class A {
public:
A() = default;
A(const A&) = delete;
A(A&&) = default;
A& operator=(A&&) = default;
};
Upvotes: 11
Reputation: 227418
Return value optimization (RVO and NRVO) does not mean the requirement that the types involved by copyable or movable is dropped. This requirement applies, whether you get RVO or not.
The most likely reason this is so is that copy elision is not (currently) enforced. It is an optimization that may take place, and it would not make sense for code to compile or not based on whether that optimization is applied in a particular implementation.
In C++17, RVO wil be enforced in some circumstances, and the requirements of copyability and movability will be dropped.
Upvotes: 7