13

I am trying to return a std::unique_ptr class member (trying to move the ownership) to the caller. The following is a sample code snippet:

class A {
public:
  A() : p {new int{10}} {}

  static std::unique_ptr<int> Foo(A &a) {
    return a.p; // ERROR: Copy constructor getting invoked
                // return std::move(a.p); WORKS FINE
  }

  std::unique_ptr<int> p;
};

I thought the compiler (gcc-5.2.1) would be able to do return value optimization (copy elision) in this case without requiring the explicit intent via std::move(). But that isn't the case. Why not?

The following code seems to be working fine, which seems equivalent:

std::unique_ptr<int> foo() {
  std::unique_ptr<int> p {new int{10}};
  return p;
}
Barry
  • 286,269
  • 29
  • 621
  • 977
axg
  • 133
  • 4

2 Answers2

11

The rule in [class.copy] is:

[...] when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

In this example:

std::unique_ptr<int> foo() {
  std::unique_ptr<int> p {new int{10}};
  return p;
}

p is the name of an object with automatic storage duration declared in the body of the function. So rather than copying it into the return value, we first try to move it. That works fine.

But in this example:

static std::unique_ptr<int> Foo(A &a) {
    return a.p;
}

that doesn't apply. a.p isn't the name of an object at all, so we don't try overload resolution as if it were an rvalue, we instead just do the normal thing: try to copy it. This fails, so you have to explicitly move() it.


That's the wording of the rule, but it might not answer your question. Why is this the rule? Basically - we're trying to be safe. If we're naming a local variable, it's always safe to move from it in a return statement. It will never be accessed again. Easy optimization, no possible downside. But in your original example, a isn't owned by this function, neither is a.p. It's not inherently safe to move from it, so the language won't try to do it automatically.

Barry
  • 286,269
  • 29
  • 621
  • 977
-1

Copy elision can't apply (among other reasons) since a.p is a std::unique_ptr, which is uncopyable. And since a.p has a lifetime beyond the body of A::Foo(A&), it would be very surprising (as in, surprising to the person writing the code) if the compiler automatically tried to move from a.p, which would likely wreck the class invariants of a. It would work if you return std::move(a.p);, but that explicitly steals a.p.

Andre Kostur
  • 770
  • 1
  • 6
  • 15
  • Copy elision can apply to move-only types (like OP's second example) – Barry Oct 01 '16 at 16:38
  • @Barry OK, my first sentence is wrong. Copy elision also applies to moves. However, I believe the rest of the answer still stands. – Andre Kostur Oct 03 '16 at 22:31
  • @AndreKostur: Since the coder is explicitly trying to return a unique_ptr whose copy constructor is deleted, the only option is for a move to happen and copy elision should work fine (IMO). What actually surprised me was the compile error rather than a warning. – axg Oct 04 '16 at 16:43