Move element from boost multi_index array

后端 未结 2 684
天命终不由人
天命终不由人 2020-12-03 23:41

Let\'s say I have movable and not copyable object and I have boost multi-index array with random_access index. I need to move my object out of array front, but I cannot find

相关标签:
2条回答
  • 2020-12-04 00:34

    Adding to @sehe's answer, the following shows how to modify the code in case your moveable type is not default constructible:

    Edited: changed code to properly deal with destruction of *extracted.
    Edited: added alternative with std::unique_ptr.
    Edited: added a second altrnative by sehe.

    Live On Coliru

    #include <boost/multi_index_container.hpp>
    #include <boost/multi_index/random_access_index.hpp>
    #include <iostream>
    #include <type_traits>
    
    struct moveonly {
        int x;
        moveonly(int x) noexcept : x(x) {}
        moveonly(moveonly&& o) noexcept : x(o.x) { o = {-1}; }
        moveonly& operator=(moveonly o) noexcept { using std::swap; swap(x, o.x); return *this; }
    };
    
    static_assert(not std::is_copy_constructible<moveonly>{}, "moveonly");
    
    namespace bmi = boost::multi_index;
    using Table   = bmi::multi_index_container<moveonly,
        bmi::indexed_by<
            bmi::random_access<bmi::tag<struct _ra> >
        > >;
    
    template <typename Container>
    void dump(std::ostream& os, Container const& c) { 
        for (auto& r: c) os << r.x << " ";
        os << "\n";
    }
    
    moveonly pop_front(Table& table) {
        std::aligned_storage<sizeof(moveonly), alignof(moveonly)>::type buffer;
        moveonly* extracted = reinterpret_cast<moveonly*>(&buffer);
    
        auto it = table.begin();
        if (it == table.end())
            throw std::logic_error("pop_front");
    
        if (table.modify(it, [&](moveonly& v) { new (extracted) moveonly{std::move(v)}; })) {
            table.erase(it);
        }
    
        try {
            moveonly ret = std::move(*extracted);
            extracted->~moveonly();
            return ret;
        } catch(...) {
            extracted->~moveonly();
            throw;
        }
    }
    
    int main() {
        Table table;
    
        table.push_back({1});
        table.push_back({2});
        table.push_back({3});
    
        dump(std::cout << "table before: ", table);
    
        std::cout << "Extracted: " << pop_front(table).x << "\n";
    
        dump(std::cout << "table after: ", table);
    }
    

    Same thing using std::unique_ptr for cleanup:

    Live On Coliru

    #include <boost/multi_index_container.hpp>
    #include <boost/multi_index/random_access_index.hpp>
    #include <iostream>
    #include <memory>
    #include <type_traits>
    
    struct moveonly {
        int x;
        moveonly(int x) noexcept : x(x) {}
        moveonly(moveonly&& o) noexcept : x(o.x) { o = {-1}; }
        moveonly& operator=(moveonly o) noexcept { using std::swap; swap(x, o.x); return *this; }
    };
    
    static_assert(not std::is_copy_constructible<moveonly>{}, "moveonly");
    
    namespace bmi = boost::multi_index;
    using Table   = bmi::multi_index_container<moveonly,
        bmi::indexed_by<
            bmi::random_access<bmi::tag<struct _ra> >
        > >;
    
    template <typename Container>
    void dump(std::ostream& os, Container const& c) { 
        for (auto& r: c) os << r.x << " ";
        os << "\n";
    }
    
    moveonly pop_front(Table& table) {
        std::aligned_storage<sizeof(moveonly), alignof(moveonly)>::type buffer;
        moveonly* extracted = reinterpret_cast<moveonly*>(&buffer);
    
        auto it = table.begin();
        if (it == table.end())
            throw std::logic_error("pop_front");
    
        if (table.modify(it, [&](moveonly& v) { new (extracted) moveonly{std::move(v)}; })) {
            table.erase(it);
        }
    
        std::unique_ptr<moveonly,void(*)(moveonly*)> ptr = {
            extracted,
            [](moveonly* p){ p->~moveonly(); }
        };
    
        return std::move(*extracted);
    }
    
    int main() {
        Table table;
    
        table.push_back({1});
        table.push_back({2});
        table.push_back({3});
    
        dump(std::cout << "table before: ", table);
    
        std::cout << "Extracted: " << pop_front(table).x << "\n";
    
        dump(std::cout << "table after: ", table);
    }
    

    Sehe provides yet another alternative based on boost::optional which is the most elegant of all:

    Live On Coliru

    #include <boost/multi_index_container.hpp>
    #include <boost/optional.hpp>
    #include <boost/multi_index/random_access_index.hpp>
    #include <iostream>
    #include <memory>
    #include <type_traits>
    
    struct moveonly {
        int x;
        moveonly(int x) noexcept : x(x) {}
        moveonly(moveonly&& o) noexcept : x(o.x) { o = {-1}; }
        moveonly& operator=(moveonly o) noexcept { using std::swap; swap(x, o.x); return *this; }
    };
    
    static_assert(not std::is_copy_constructible<moveonly>{}, "moveonly");
    
    namespace bmi = boost::multi_index;
    using Table   = bmi::multi_index_container<moveonly,
        bmi::indexed_by<
            bmi::random_access<bmi::tag<struct _ra> >
        > >;
    
    template <typename Container>
    void dump(std::ostream& os, Container const& c) { 
        for (auto& r: c) os << r.x << " ";
        os << "\n";
    }
    
    moveonly pop_front(Table& table) {
        boost::optional<moveonly> extracted;
    
        auto it = table.begin();
        if (it == table.end())
            throw std::logic_error("pop_front");
    
        if (table.modify(it, [&](moveonly& v) { extracted = std::move(v); })) {
            table.erase(it);
        }
    
        return std::move(*extracted);
    }
    
    int main() {
        Table table;
    
        table.push_back({1});
        table.push_back({2});
        table.push_back({3});
    
        dump(std::cout << "table before: ", table);
    
        std::cout << "Extracted: " << pop_front(table).x << "\n";
    
        dump(std::cout << "table after: ", table);
    }
    
    0 讨论(0)
  • 2020-12-04 00:38

    Non-const element operations are not supported because they could leave elements in a state which would break invariants placed on them by the various indexes.

    The closest thing you can do is using modify:

    moveonly pop_front(Table& table) {
        moveonly extracted;
    
        auto it = table.begin();
        if (it == table.end())
            throw std::logic_error("pop_front");
    
        if (table.modify(it, [&](moveonly& v) { extracted = std::move(v); })) {
            table.erase(it);
        }
    
        return extracted;
    }
    

    Note that modify does incur the cost of checking all indexes, and may fail. Fortunately, if it does fail, the effect is that iterator is erased:

    • Effects: Calls mod(e) where e is the element pointed to by position and rearranges *position into all the indices of the multi_index_container. Rearrangement on sequenced indices does not change the position of the element with respect to the index; rearrangement on other indices may or might not succeed. If the rearrangement fails, the element is erased.
    • Postconditions: Validity of position is preserved if the operation succeeds.

    And here's a live demo:

    Live On Coliru

    #include <boost/multi_index_container.hpp>
    #include <boost/multi_index/random_access_index.hpp>
    #include <iostream>
    
    struct moveonly {
        int x;
        moveonly(int x = -1) noexcept : x(x) {}
        moveonly(moveonly&& o) noexcept : x(o.x) { o = {}; }
        moveonly& operator=(moveonly o) noexcept { using std::swap; swap(x, o.x); return *this; }
    };
    
    static_assert(not std::is_copy_constructible<moveonly>{}, "moveonly");
    
    namespace bmi = boost::multi_index;
    using Table   = bmi::multi_index_container<moveonly,
        bmi::indexed_by<
            bmi::random_access<bmi::tag<struct _ra> >
        > >;
    
    template <typename Container>
    void dump(std::ostream& os, Container const& c) { 
        for (auto& r: c) os << r.x << " ";
        os << "\n";
    }
    
    moveonly pop_front(Table& table) {
        moveonly extracted;
    
        auto it = table.begin();
        if (it == table.end())
            throw std::logic_error("pop_front");
    
        if (table.modify(it, [&](moveonly& v) { extracted = std::move(v); })) {
            table.erase(it);
        }
    
        return extracted;
    }
    
    int main() {
        Table table;
    
        table.push_back({1});
        table.push_back({2});
        table.push_back({3});
    
        dump(std::cout << "table before: ", table);
    
        std::cout << "Extracted: " << pop_front(table).x << "\n";
    
        dump(std::cout << "table after: ", table);
    }
    

    Which prints:

    table before: 1 2 3 
    Extracted: 1
    table after: 2 3 
    
    0 讨论(0)
提交回复
热议问题