我如何使用 emplace for unordered_set 将 shared_ptr 保存到对象?

How I can use emplace for unordered_set that holds shared_ptr to the object?

假设我有一个对象:

struct Foo {
    Foo(const std::string& str1, const std::string& str1) 
        : mStr1(str1), mStr2(str2)
    {}

    std::string mStr1;
    std::string mStr2;
};

并设置

typedef std::unordered_set<std::shared_ptr<Foo> , Hash, Compare> Set;

我有自定义哈希器并进行比较。但是当我说:

Set set;
set.emplace(str1, str2);

我收到编译错误,因为 Foo 的构造函数显然不是 std::shared_ptr<Foo> 的构造函数。我想要的是当 emplace 需要构造一个指针来使用 std::make_shared<Foo>(str1, str2)

看来我还需要一个自定义分配器,但我没有设法实现一个满足编译器要求的分配器。

我的问题是:我想要的是可能的。如果是,分配器如何是正确的方法。如果是的话,你能给我举个例子吗?

I receive compile error, because the constructor of Foo is obviously not a constructor of std::shared_ptr. What I would like is when emplace needs to construct a pointer to use std::make_shared(str1, str2)

emplace 被实现为一个函数,它使用完美转发来调用所包含元素的构造函数(在本例中为 shared_ptr)。包含的元素的构造函数接受指向 Foo 的指针,因此您应该能够这样做(就像构造 shared_ptr<Foo> 对象一样):

set.emplace(new Foo("x", "y")); //or
set.emplace(new Foo(str1, str2)); 

It seems that I also need a custom allocator for that, but I did not manage to implement one that satisfy the compiler.

如果您只想以最有效的方式添加 shared_ptr(通过在某些预分配元素上调用转发构造函数),那么自定义分配器完全是矫枉过正,否则我完全误解了你的问题。如果您不希望使用默认分配器(使用 operator new)构造元素,则通常会使用分配器。在这种情况下,shared_ptr 本身将是将在堆上构造的元素。如果您担心堆分配由于某种原因对您的目的来说效率低下(例如,如果您分配数百万个小对象),您只会使用分配器。

注意(正如@Yakk 所评论的),在这种情况下,shared_ptr 的实例化可能会抛出(我只能认为 bad_alloc 是可能的),在这种情况下传递给 emplace 的指针会导致泄漏。出于这个原因,我也认为 std::make_shared 会是一个更好的选择(如另一个答案中所述)。

您可以直接在 emplace 的参数列表中使用 std::make_shared

set.emplace(std::make_shared<Foo>(str1, str2));

不需要自定义分配器。

使用set.insert(std::make_shared<Foo>(str1, str2));emplace 当重复键是一个问题时,对于具有唯一键的容器来说通常不是一个增益,因为它的操作方式:

  • 它必须先构造对象,然后才能将它与容器中的现有键进行比较,以确定它是否可以插入。
  • 一旦对象被构建,就不能被复制或移动,因为对象不需要是可复制或可移动的。此外,emplace 没有可靠的方法来检测它何时 可以 复制或移动某些东西,因为 is_copy_constructible returns 有很多类型true但实际无法复制
  • 对象构造只能发生一次,因为构造函数可能会从参数中移动或产生其他副作用。

emplace 的典型实现因此总是预先为节点分配内存,在该内存中构造对象,将其与容器中的现有元素进行比较,然后将其链接或销毁它并释放内存。

另一方面,

insert 拥有随时可用的密钥。因此它可以先决定该节点是否应该被插入,只有在应该插入时才分配内存。

从理论上讲,对于 "one argument with the same type as the element type" 情况,实现可能是特殊情况 emplace。但我知道没有实际执行此操作的实现。