1

Consider the two constructors below:

// Passing data_ by r-value reference:
class A
{
public:

    A(std::unique_ptr<int>&& data_);

    // ...
};

// Passing data_ by value:
class B
{
public:

    B(std::unique_ptr<int> data_);

    // ...
};

I have been asking myself which constructor was better for taking ownership of data_. This nice answer seems to indicate that the approach by r-value reference may not be the best way. From the recommendations section of this answer:

(D) By r-value reference: If a function may or may not claim ownership (depending on internal code paths), then take it by &&. But I strongly advise against doing this whenever possible.

I have tried to write a unit test that would show a difference, using the r-value approach, with the by-value approach but failed. In all cases, both constructors work the same and the data is always moved. The author of the answer seems to indicate that depending on internal code paths, the move could be ignored. Citing the answer further:

The problem is that it hasn't. It is not guaranteed to have been moved from. It may have been moved from, but you will only know by looking at the source code. You cannot tell just from the function signature.

Could anyone please give me an example of such a scenario where the move could be ignored?

BobMorane
  • 3,870
  • 3
  • 20
  • 42
  • 2
    Have a "move-reference parameter" (one with `&&`) and doing a `std::move` does not actually move anything. It merely *allows* a move to take place. In my own projects, the team convention strongly prefers `B`, and `A` (passing a unique_ptr by move-reference) would be called out on a code review. Also, there is a strong distinction (in our code reviews) for pass-for-use versus passing-ownership; a `unique_ptr` would never be a parameter unless it was to pass ownership. [GotW #91](https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/) – Eljay Mar 21 '23 at 12:06

2 Answers2

2

For example in the below code neither class uses the passed in pointer:

#include <memory>
#include <iostream>

class A
{
public:

    A(std::unique_ptr<int>&& data_) {}
};

// Passing data_ by value:
class B
{
public:

    B(std::unique_ptr<int> data_) {}
};

int main()
{
    auto a = std::make_unique<int>(5);
    A{std::move(a)};
    auto b = std::make_unique<int>(5);
    B{std::move(b)};
    std::cout << (a == nullptr) << "\n";
    std::cout << (b == nullptr) << "\n";
}

https://godbolt.org/z/dbbcKbs79

In A the pointer is passed by r-value reference into data_, the reference isn't used so the original a pointer is unmodified.

In B the pointer is moved into the temporary value data_, the pointer isn't used but the original b pointer is still reset to null.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
1

You haven't shown enough here. Normally, we pass by value when transferring ownership:

class B
{
    std::unique_ptr<int> data;

public:
    B(std::unique_ptr<int> data)
      : data{std::move(data)}
    {}
};

Don't use std::unique_ptr at all where no transfer is intended (use a raw pointer, or perhaps a reference if it's guaranteed non-null). If ownership may or may not be transferred, depending on something unknown at compile-time, then that's the single (and exceedingly rare) use for passing by rvalue-reference. I've never needed to do that in my entire career.


One specific benefit to passing by value is that if the constructor throws before its data is assigned, the temporary copy in the parameter will get destructed, and we don't end up failing to move from the caller.

But the main benefit is clarity - instead of readers asking why we're taking a reference to the caller's pointer, we simply do the obvious and expected thing.

When passing by value, the target object is definitely no longer owned by the caller. When passed by reference, we don't know whether the new A has taken ownership or not, unless we inspect A's implementation.

In simpler terms, B() is unequivocally a sink, whereas A() may or may not be.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
  • Yes, this is why I have written "I have been asking myself which constructor was better for *taking ownership* of `data_`" in my question. I am trying to understand why, in this case, taking the unique ptr by value is usually preferred. – BobMorane Mar 21 '23 at 13:36