26

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?

peterh
  • 11,875
  • 18
  • 85
  • 108

3 Answers3

23

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.

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
11

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

Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
  • I can't understand the down, especially without explanation. – peterh Jul 06 '16 at 13:16
  • 1
    @peterh Probably because it isn't answering the question you asked. – juanchopanza Jul 06 '16 at 13:17
  • Ok, but why the move constructors are needed? The only constructur which required to be called in this example, is the empty constructor. No moving actually happen. – peterh Jul 06 '16 at 13:18
  • 1
    @juanchopanza It is possible, but it is not clear to me, why. What is not clear in this answer, why would be the move constructors needed. – peterh Jul 06 '16 at 13:19
  • 2
    @juanchopanza It is answering the question, albeit poorly due to the lack of an explanation. This is a valid way of guaranteeing that the class is never copied while still allowing the OP's `return` statement to work. –  Jul 06 '16 at 13:20
  • 1
    @hvd I can discern to questions. How to enforce copy elision, and why is the copy constructor needed. I can't see an answer to either. – juanchopanza Jul 06 '16 at 13:21
  • 3
    @peterh In practice, there are no move going on, but semantically there must be a copy or a move, because C++ returns by value. In C++98, that means a copy constructor is always needed. In C++11, you can substitute it for a move constructor. In C++17, in certain cases, neither will be needed – KABoissonneault Jul 06 '16 at 13:22
  • 3
    @juanchopanza The third question is the implicit "How do I do this?" after "I want to guarantee that it won't be ever copyied" :) –  Jul 06 '16 at 13:22
  • @KABoissonneault My goal was to make `A a = fun();` to work as `A a; fun(&a) `, i.e. even without the need of a movement. But I didn't mention in the question clearly, so this answer will be useful for the googlers of the future. – peterh Jul 06 '16 at 17:46
  • @peterh The thing is, you can make it work the way you want. You just have to `move` in the implementation, not when calling `f`. Just try it. Your runtime won't even notice – KABoissonneault Jul 06 '16 at 17:59
  • Great answer! Thanks – adamkonrad Nov 30 '18 at 18:31
7

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.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480