这是设计还是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);
}
导致分段故障
2条答案
按热度按时间5fjcxozz1#
为了记录在案,我的答案发布在Github上的相关问题上。
如果插入不成功,Boost.MultiIndex插入确实不会修改传递的
value_type&&
。这条线的问题在于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
将变为空,无论插入是否成功。一个可能的用户端解决方法是使用这样的东西:
(https://godbolt.org/z/P5KW6q585)
v2g6jxz62#
注意我的答案错过了the real cause of the problem。我将保留关于标准库行为的潜在信息位,您似乎对这些信息也有过于乐观的期望,因为这些信息对其他人可能仍然有帮助。
事实上,即使插入失败,相应的标准库函数(如
map::emplace
/map::insert
)也可能移动。我认为这是在权衡性能成本的同时保持强大的异常安全保证(is documented)。C++17添加了新的操作
insert_or_assign
和try_emplace
,专门用于添加该保证:注意事项
与
insert
或emplace
不同,* 这些函数不会在插入不发生的情况下从右值参数中移动 *,这使得操作其值为仅移动类型的map(如std::map<std::string, std::unique_ptr<foo>>
)变得容易。此外,try_emplace
将mapped_type
的键和参数分开处理,这与emplace
不同,emplace
需要参数来构造value_type
(即std::pair
)。