Alexey Starinsky
Alexey Starinsky

Reputation: 4337

A uniform copy and move constructor of a template class

Let assume I have a DataProcessor template class that holds a smart pointer to the processed data that has operator * and operator ->:

template <class DataPointer>
class DataProcessor
{
    public:

        //it is not clear how to implement the constructor
        //DataProcessor(DataPointer p) : pData(p) {};

        void Process() { /* do something with *pData */ };

    private:

        DataPointer pData;
}

How to implement the constructor to make DataProcessor work with both std::unique_ptr (the constructor should accept it by && and move it) and std::shared_ptr (the constructor should accept it by & and copy it)? Is it possible to have some kind of a uniform constructor?

Actually I have a class that holds a smart Win32 handle UniqueHandle and SharedHandle that have the similar semantics like std::unique_ptr and std::shared_ptr. So it is a general question on how to implement a scenario like this.

Upvotes: 0

Views: 488

Answers (1)

Nicol Bolas
Nicol Bolas

Reputation: 473996

Your choices are essentially these:

  1. Take the parameter by value:

    DataProcessor(DataPointer p) : pData(std::move(p)) {}
    

    If DataPointer is move-only, then the user will have to call it through std::move, which will move-construct p, which is then used to move-construct pData. If it is copyable, then p will be copy/move constructed based on how the user passes the value. From there, it will move construct pData.

    Note that this version adds an additional move operation.

  2. Take the parameter by rvalue reference, always:

    DataProcessor(DataPointer &&p) : pData(std::move(p)) {}
    

    In this case, if DataPointer is not move-only, and the user wants to pass an lvalue, the user must explicitly copy the value into a temporary used to initialize p. That would look something like this:

    DataProcessor<shared_ptr<T>> dp(shared_ptr{sp});
    

    Where sp is an existing shared_ptr that you want to copy from. This does only one move when given an object to move from, but does a copy+move when copying.

  3. Write two functions, employing SFINAE to remove the copying version if DataPointer is non-copyable. This version has the advantage of doing no additional moves:

DataProcessor(DataPointer &&p) : pData(std::move(p)) {}
template<typename T = DataPointer>
DataProcessor(std::enable_if_t<std::is_copy_constructible_v<T>, const T&> p)
        : pData(p) {}

Upvotes: 2

Related Questions