Search code examples
c++c++11memorycontainersmove

How to get a move-only type out of a STL container?


Let's consider an std::unordered_set of std::unique_ptr<T> as an example. Can I move an element of the set elsewhere ?

#include <unordered_set>
#include <iostream>
#include <memory>
#include <vector>

int main()
{
    std::unordered_set<std::unique_ptr<int>> mySet;

    mySet.insert(std::make_unique<int>(1));
    mySet.insert(std::make_unique<int>(2));
    mySet.insert(std::make_unique<int>(3));

    std::vector<std::unique_ptr<int>> myVector;

    for (auto&& element : mySet)
    {
        std::cout << *element << std::endl;
        //myVector.push_back(element); won't compile as you can only get a const ref to the key
    }
}

I have a very practical example of code where I would like to do this but am reduced to use a std::shared_ptr. Do you know of another (better ?) alternative ?


Solution

  • In C++03, C++11, and C++14, not directly. You'd have to change the type to be something like:

    template <class T>
    struct handle {
        mutable std::unique_ptr<T> owning_ptr;
        T* observing_ptr; // enforce that observing_ptr == owning_ptr.get() on construction
    
        // define operator<, hash, etc. in terms of the observing ptr
    };
    

    With this, you can write:

    std::unordered_set<handle<int>> mySet;
    // initialize as appropriate
    
    for (auto& elem : mySet) {
        myVector.push_back(std::move(elem.owning_ptr));        
    }
    mySet.clear();
    

    This will still be well-defined behavior because we're not messing with any of the container internals - the observing pointer will still be valid through the end of the clear(), just now myVector owns it instead.


    In C++17, we can do this directly and more simply with the help of extract():

    for (auto it = mySet.begin(); it != mySet.end();  
    {
        std::cout << **it << std::endl;
        myVector.push_back(std::move(
            mySet.extract(it++).value()));
    }