从 boost multi_index 数组中移动元素

Posted

技术标签:

【中文标题】从 boost multi_index 数组中移动元素【英文标题】:Move element from boost multi_index array 【发布时间】:2017-09-06 18:51:09 【问题描述】:

假设我有可移动且不可复制的对象,并且我有带有 random_access 索引的 boost 多索引数组。我需要将我的对象移出数组前面,但我找不到任何可以在documentation 中为我提供右值/左值引用的方法。我只能看到 front() 给我恒定的参考和 pop_front() 删除元素,但不返回任何东西。那么有没有办法将元素移出 boost 多索引?

【问题讨论】:

【参考方案1】:

添加到@sehe 的答案,以下显示了如何修改代码以防您的可移动类型不是默认可构造的:

已编辑:更改了代码以正确处理 *extracted 的破坏。已编辑:添加了 std::unique_ptr 的替代方案。已编辑:添加了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) moveonlystd::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);

同样的事情使用std::unique_ptr 进行清理:

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) moveonlystd::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 提供了另一种基于boost::optional 的替代方案,这是最优雅的:

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);

【讨论】:

啊。这比我想象的要简单。不知何故,处理原始存储比严格要求更让我害怕:) 不幸的是,重建索引的成本难以预测,这使得将元素更改为可复制比使用此解决方案更受欢迎。我很惊讶 element_type&amp;&amp; move_front(); 和更通用的 element_type &amp;&amp;extract( iterator it ); 没有实现。是否有并发症或没有人要求?该方法似乎并不比简单的pop_front()erase() 复杂得多,但我不知道内部原理。 顺便说一句,为什么不使用std::unique_ptr&lt;moveonly&gt;,最后只使用return std::move( *ptr );,这样可以消除放置新的复杂性并创建不必要的实例。 @Slava 使用 std::unique_ptr 而不使用自定义删除器意味着在此过程中存在堆分配,这通常比您可以用作替代方案的任何基于堆栈的东西更昂贵。 看反汇编,似乎在内部使用boost::optional&lt;moveonly&gt; 做了对齐存储的“复杂”事情:coliru.stacked-crooked.com/a/b8908ffafeecc228【参考方案2】:

不支持非 const 元素操作,因为它们可能会使元素处于破坏由各种索引放置在其上的不变量的状态。

您可以做的最接近的事情是使用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;

请注意,modify 确实会产生检查所有索引的成本,并且可能会失败。幸运的是,如果它确实失败了,效果就是 iterator 被删除:

效果:调用 mod(e),其中 e 是 position 指向的元素,并将 *position 重新排列到 multi_index_container 的所有索引中。对有序索引的重新排列不会改变元素相对于索引的位置;对其他指数的重新排列可能会也可能不会成功。 如果重新排列失败,则删除元素。 后置条件:如果操作成功,则保留位置的有效性。

这是一个现场演示:

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);

哪些打印:

table before: 1 2 3 
Extracted: 1
table after: 2 3 

【讨论】:

我明白了,为什么不允许修改对象。我不明白为什么没有办法从数组中提取元素而不仅仅是删除它。这种方法是一种非常糟糕的解决方法,因为可能不必要的昂贵(重新排列索引可能会很昂贵)并且它还对元素提出了不必要的要求。 顺便说一句,如果modify 返回 false 它可能应该只是返回,而不是抛出异常。因为在这种情况下元素将被删除(我们实际需要的) @Slava Re:第二条评论;是的。在这个看起来不错的特定操作中。当我编写代码时,我还没有完全意识到整个操作中完全迭代器稳定性的含义。更新了答案。 stable_vector 可能更接近random_access 索引,但可能不是我需要的。我正在尝试实现的是能够通过附加键查找和删除任务的优先级队列。但我认为这些细节在这里是无关紧要和不必要的。 @sehe 好的,我自己做了。期待您对 extract/merge 的反馈。

以上是关于从 boost multi_index 数组中移动元素的主要内容,如果未能解决你的问题,请参考以下文章

在模板结构中使用 boost multi_index

boost multi_index 提取的键是不是被缓存?

多索引表 boost::multi_index多索引容器

通过 size_t 索引迭代到 boost::multi_index 中的顺序位置?

从 GitHub 安装 Boost 特定模块

提升 multi_index 获取依赖类型