Search code examples
c++c++11abstractunique-ptrmake-shared

shared_ptr from unique_ptr of abstract class


I'm trying to follow Herb Sutter's C++ guidelines, in this case to prefer unique_ptr to raw pointers and shared_ptr. One of the arguments in favour of std::unique_ptr is convertibility to shared_ptr should that be needed at some point.

In my case I have a vector of unique_ptr that I need to pass to a method that takes a vector of shared_ptr. I was hoping to be able to write something like:

for (auto &uniquePtr : vectorUnique)
    vectorShared.push_back(make_shared<Abstract>(move(uniquePtr));

This gives me the following error with my Xcode 7.1 based toolchain configured for C++11:

error: field type 'Abstract' is an abstract class.

It seems the STL is trying to hold a concrete instance of type Abstract when I use make_shared. That would seem to make Mr. Sutter's advice unworkable in many cases, so I'm sure I must be doing something wrong! I've resorted to writing:

for (auto &uniquePtr : vectorUnique) {
    auto ptr = uniquePtr.get();
    auto shared = shared_ptr<Abstract>(ptr);
    vectorShared.push_back(shared);
    uniquePtr.release();
}

Is there a better way to do this?


Solution

  • make_shared constructs a new object using the given arguments and returns a shared_ptr to it. So the compiler expects a constructor Abstract(std::unique_ptr<Abstract>), which is probably not what you have.

    What you want is the constructor of shared_ptr that takes a unique_ptr argument:

        vectorShared.push_back(shared_ptr<Abstract>(move(uniquePtr)));
    

    and, as it's not explicit, then

        vectorShared.emplace_back(move(uniquePtr));
    

    will just work (I've used emplace_back to avoid redundant copying, at Richard Hodges's suggestion). There's even a standard algorithm, so you don't need the for loop:

        std::move(vectorUnique.begin(), vectorUnique.end(),
                  std::back_inserter(vectorShared));
    

    If you need this regularly, you might define a function:

    #include <vector>
    #include <memory>
    #include <algorithm>
    
    template<typename T>
    std::vector<std::shared_ptr<T>>
            convert_to_shared(std::vector<std::unique_ptr<T>>&& vu)
    {
        using std::begin;
        using std::end;
        std::vector<std::shared_ptr<T>> vs;
        vs.reserve(vu.size());
        std::move(begin(vu), end(vu), std::back_inserter(vs));
        return vs;
    }
    
    
    // Example of use
    class Abstract {};
    int main()
    {
        std::vector<std::unique_ptr<Abstract>> vectorUnique;
        std::vector<std::shared_ptr<Abstract>> vectorShared
            = convert_to_shared(std::move(vectorUnique));
    }
    

    Sorry about the terrible name (I'm open to suggestions). If you omit the call to reserve(), you could generalise it to more containers.