c++ MultiIndex::insert(value_type&& x)即使在插入失败的情况下也会破坏x

xytpbqjk  于 12个月前  发布在  其他
关注(0)|答案(2)|浏览(105)

这是设计还是bug?
通常情况下,我希望在插入失败的情况下,插入不会破坏移入参数。
https://godbolt.org/z/bofeMo8fd

#include <memory>
#include <vector>

#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/indexed_by.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/tag.hpp>
#include <boost/multi_index_container.hpp>


class Base {
  public:
    virtual size_t GetId() const = 0;
    virtual ~Base() = default;
};

class Derived : public Base {
  public:
    size_t GetId() const { return 42; }
};

int main(int, char**) {
    // A tag to view elements in the order of insertion.
    struct Sequenced{};
    // A tag to view elements in the order sorted by type id.
    struct Ordered{};

    using ContainerType = boost::multi_index_container<
        // Elements are pointers to Base.
        std::unique_ptr<Base>,

        boost::multi_index::indexed_by<
            boost::multi_index::sequenced<boost::multi_index::tag<Sequenced>>,
            boost::multi_index::hashed_unique<
              boost::multi_index::tag<Ordered>,
              boost::multi_index::const_mem_fun<Base, size_t,
                                                &Base::GetId>>>>;

    ContainerType container;

    // Insert first element.
    auto& ordered_view = container.get<Ordered>();
    auto new_element = std::make_unique<Derived>();
    auto insert_result = ordered_view.insert(std::move(new_element));
    if (!insert_result.second) return -1;

    // Try toInsert second element with the same key.
    auto new_element2 = std::make_unique<Derived>();
    auto insert_result2 = ordered_view.insert(std::move(new_element2));
    assert(!insert_result2.second);
    assert(new_element2->GetId() == 42);
}

导致分段故障

5fjcxozz

5fjcxozz1#

为了记录在案,我的答案发布在Github上的相关问题上。
如果插入不成功,Boost.MultiIndex插入确实不会修改传递的value_type&&。这条线的问题在于

ordered_view.insert(std::move(new_element))

insert需要一个std::unique_ptr<Base>&&,而你传递的是一个std::unique_ptr<Derived>&&:按照std::unique_ptr的隐式构造方式,这会创建一个临时std::unique_ptr<Base>&&new_element的值将被转移到该临时std::unique_ptr<Base>&&。所有这些都发生在Boost.MultiIndex被调用之前,最终结果是new_element将变为空,无论插入是否成功。
一个可能的用户端解决方法是使用这样的东西:

template<typename Container, typename Derived>
auto insert_unique_ptr(Container& c, std::unique_ptr<Derived>&& p)
{
  typename Container::value_type pb = std::move(p);
  auto res = c.insert(std::move(pb));
  p.reset(static_cast<Derived*>(pb.release()));
  return res;
}

https://godbolt.org/z/P5KW6q585

v2g6jxz6

v2g6jxz62#

注意我的答案错过了the real cause of the problem。我将保留关于标准库行为的潜在信息位,您似乎对这些信息也有过于乐观的期望,因为这些信息对其他人可能仍然有帮助。
事实上,即使插入失败,相应的标准库函数(如map::emplace/map::insert)也可能移动。我认为这是在权衡性能成本的同时保持强大的异常安全保证(is documented)。
C++17添加了新的操作insert_or_assigntry_emplace,专门用于添加该保证:

注意事项

insertemplace不同,* 这些函数不会在插入不发生的情况下从右值参数中移动 *,这使得操作其值为仅移动类型的map(如std::map<std::string, std::unique_ptr<foo>>)变得容易。此外,try_emplacemapped_type的键和参数分开处理,这与emplace不同,emplace需要参数来构造value_type(即std::pair)。

相关问题