1

I've written a simple linked list because a recent interview programming challenge showed me how rusty my C++ has gotten. On my list I declared a private copy constructor because I wanted to explicitly avoid making any copies (and of course, laziness). I ran in to some trouble when I wanted to return an object by value that owns one of my lists.

class Foo
{
   MyList<int> list;  // MyList has private copy constructor

   public:
      Foo() {};
};

class Bar
{
   public:
      Bar() {};

      Foo getFoo()
      {
         return Foo();
      }
};

I get a compiler error saying that MyList has a private copy constructor when I try to return a Foo object by value. Should Return-Value-Optimization negate the need for any copying? Am I required to write a copy constructor? I'd never heard of move constructors until I started looking for solutions to this problem, is that the best solution? If so, I'll have to read up on them. If not, what is the preferred way to solve this problem?

jlunavtgrad
  • 997
  • 1
  • 11
  • 21
  • 1
    RVO is an optimization. If you don't allow copy construction, then you won't be able to copy construct. RVO could be applied if you did allowed it. – mfontanini Jun 26 '12 at 14:19
  • Optimizations should not change behavior, on the contrary, compilers may only perform optimization when they can guarantee the expected behavior. To return by copy, the object must be copyable. – lvella Jun 26 '12 at 14:23
  • 3
    @lvella: Although in this case, the optimisation *is* allowed to change behaviour - the copy can be omitted even if it has side effects. But you're correct that the copy constructor must be accessible. – Mike Seymour Jun 26 '12 at 14:25
  • 1
    @lvella RVO is allowed to change behaviour. But the code must be legal without RVO, so the compiler still expects to find a valid copy constructor. – juanchopanza Jun 26 '12 at 14:26

3 Answers3

5

The standard explicitly states that the constructor still needs to be accessible, even if it is optimized away. See 12.8/32 in a recent draft.

I prefer making an object movable and non-copyable in such situations. It makes ownership very clear and explicit.

Otherwise, your users can always use a shared_ptr. Hiding shared ownership is at best a questionable idea (unless you can guarantee all your values are immutable).

pmr
  • 58,701
  • 10
  • 113
  • 156
3

The basic problem is that return by value might copy. The C++ implementation is not required by the standard to apply copy-elision where it does apply. That's why the object still has to be copyable: so that the implementation's decision when to use it doesn't affect whether the code is well-formed.

Anyway, it doesn't necessarily apply to every copy that the user might like it to. For example there is no elision of copy assignment.

I think your options are:

  • implement a proper copy. If someone ends up with a slow program due to copying it then their profiler will tell them, you don't have to make it your job to stop them if you don't want to.
  • implement a proper move, but no copy (C++11 only).
  • change getFoo to take a Foo& (or maybe Foo*) parameter, and avoid a copy by somehow mutating their object. An efficient swap would come in handy for that. This is fairly pointless if getFoo really returns a default-constructed Foo as in your example, since the caller needs to construct a Foo before they call getFoo.
  • return a dynamically-allocated Foo wrapped in a smart pointer: either auto_ptr or unique_ptr. Functions defined to create an object and transfer sole ownership to their caller should not return shared_ptr since it has no release() function.
  • provide a copy constructor but make it blow up somehow (fail to link, abort, throw an exception) if it's ever used. The problems with this are (1) it's doomed to fail but the compiler says nothing, (2) you're enforcing quality of implementation, so your class doesn't work if someone deliberately disables RVO for whatever reason.

I may have missed some.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
2

The solution would be implementing your own copy constructor that would use other methods of MyList to implement the copy semantics.

... I wanted to explicitly avoid making any copies

You have to choose. Either you can't make copies of an object, like std::istream; then you have to hold such objects in pointers/references, since these can be copied (in C++11, you can use move semantics instead). Or you implement the copy constructor, which is probably easier then solving problems on each place a copy is needed.

jpalecek
  • 47,058
  • 7
  • 102
  • 144