Home > Enterprise >  Why not to accept std::unique_ptr by rvalue reference?
Why not to accept std::unique_ptr by rvalue reference?

Time:04-20

Can somebody explain why everybody passes std::unique_ptr by value instead of by rvalue reference?

From what I've observed, this required an additional move constructor to be invoked.

Here's an example of a class holding a "pointer". It takes 3 move-ctor calls to take it by value, versus 2 calls to take it by reference:

#include <memory>
#include <iostream>

class pointer {
public:
    pointer()
    { std::cerr << "ctor" << std::endl; }
    
    pointer(const pointer&)
    { std::cerr << "copy-ctor" << std::endl; }
    
    pointer& operator=(const pointer&)
    { std::cerr << "copy-assignment" << std::endl; return *this; }
    
    pointer(pointer&&)
    { std::cerr << "move-ctor" << std::endl; }
    
    pointer& operator=(pointer&&)
    { std::cerr << "move-assignment" << std::endl; return *this; }
    
    ~pointer()
    { std::cerr << "dtor" << std::endl; }
};

class A {
public:
    // V1
    A(pointer _ptr) : ptr(std::move(_ptr)) {}
        
    // V2
    A(pointer&& _ptr) : ptr(std::move(_ptr)) {}

private:
    pointer ptr;
};

int main() {
    // Three calls to move-ctor versus two calls if pass by rvalue reference
    auto ptr = pointer();
    A a(std::move(ptr));

    // Two calls to move-ctor always
    A a(pointer{});
}

CodePudding user response:

Passing a unique_ptr by reference, rvalue or otherwise, doesn't actual move anything, so you can't know by just looking at the function declaration if a move will happen.

Passing a unique_ptr by value on the other hand guarantees that the passed in pointer will be moved from, so without even have to look at the documentation you know calling that function releases you from the pointers' ownership.

CodePudding user response:

For the same reason people pass int instead of const int&.

std::unique_ptr is just an RAII wrapper around a single pointer value, so moving it is just copying a single register width value, then zeroing the source. That's so trivial there's no real benefit to avoiding the move. After all, the cost to pass the reference (when not inlined) is the cost of passing a pointer too, so passing by reference can be less efficient (because if not inlined, it has to follow the reference to the real memory, then pull out the value from there; the top of the stack is likely in L1 cache, who knows if the place it's stored is?).

In practice, much of this will be inlined with optimizations enabled, and both approaches would get the same result. Passing by value is a good default when there's no benefit to passing by reference, so why not do it that way?

CodePudding user response:

why everybody passes the std::unique_ptr by value instead of rvalue reference?

It may be more common, but it's not "everybody".

The drawback of std::unique_ptr&& parameter is that it doesn't explicitly communicate to the caller whether the pointer will be moved from or not. It might always move, or it might depend on some condition. You would have to know the implementation or at least API documentation to know for sure. The corresponding benefit of std::unique_ptr parameter is that it alone tells the reader of the declaration that the function will take ownership of the pointer. For this reason, it may be a good choice to use std::unique_ptr parameter and probably part of the reason why it's more common.

The benefit of std::unique_ptr&& is avoiding the extra move. However, moving of a std::unique_ptr is a very fast operation. It's insignificant compared for example to the memory allocation itself. In most cases, it simply doesn't matter.

The difference between the two is fairly subtle. std::unique_ptr&& parameter may be considered in a case where you've measured the move to have significant cost. Which is not very common. Or in cases where your API may be used in cases where that cost could be significant. It's hard to prove that this won't ever happen if you're writing a public API, so it is a more likely argument to use.

  •  Tags:  
  • c
  • Related