peterh
peterh

Reputation: 1

How to enforce copy elision, why it won't work with deleted copy constructor?

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

Answers (4)

比尔盖子
比尔盖子

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

Jesper Juhl
Jesper Juhl

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

Hatted Rooster
Hatted Rooster

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;
};

Example

Upvotes: 11

juanchopanza
juanchopanza

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

Related Questions