Search code examples
c++c++20moveunique-ptr

Passing std::unique_ptr to constructor through r-value reference


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?


Solution

  • 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.