0

Why can't I return a class containing a std::unique_ptr, using std::move semantics (I thought), as in the example below? I thought that the return would invoke the move ctor of class A, which would std::move the std::unique_ptr. (I'm using gcc 11.2, C++20)

Example:

#include <memory>

class A {
  public:
    explicit A(std::unique_ptr<int> m): m_(std::move(m)) {}
  private:
    std::unique_ptr<int> m_;
};

A makit(int num) {
    auto m = std::make_unique<int>(num);
    return std::move(A(m));  // error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'x86-64 gcc 11.2 #1
}

int main() {
    auto a = makit(42);
    return 0;
}

I believe the solution is to return a std::unique_ptr, but before I give in I wonder why the move approach doesn't work.

Chris
  • 9,043
  • 4
  • 12
  • 31
voxoid
  • 1,045
  • 1
  • 13
  • 31

2 Answers2

2

I thought that the return would invoke the move ctor of class A, which would std::move the std::unique_ptr.

All true, but the move constructor only moves the members of A. It cannot move unrelated satellite unique pointers for you.

In the expression A(m), you use m as an lvalue. That will try to copy m in order to initialize the parameter m of A::A (btw, terrible naming scheme to reason about all of this). If you move that, i.e. A(std::move(m)), the expression becomes well-formed.

And on that subject, the outer std::move in std::move(A(...)) is redundant. A(...) is already an rvalue of type A. The extra std::move does nothing good here.

StoryTeller - Unslander Monica
  • 159,632
  • 21
  • 358
  • 434
-1
  • In short, you should not return A using std::move since the compiler will do the optimization work for you (through a trick called RVO: What are copy elision and return value optimization?).

  • Another point is that the std::move in m_(std::move(m)) is totally unnecessary since the coping of the unique_ptr will happen regardless of using std::move or not. Remember that std::move does not guarantee the move operation and does not prevent coping in all cases.

In conclusion, you better use return A( std::move(m) ); and not return std::move(A(m));. In your return statement, A(m) is already a prvalue and you don't need to cast it to an xvalue using std::move in order to return it efficiently. Just return it by value and the compiler will do the trick for you.

digito_evo
  • 2,536
  • 1
  • 7
  • 29