为什么 std::unordered_map 中的 emplace 方法采用多个参数?

Why does the emplace method in std::unordered_map take multiple parameters?

提问人:Zebrafish 提问时间:9/23/2023 最后编辑:Jan SchultkeZebrafish 更新时间:9/23/2023 访问量:107

问:

这对我来说是一个困惑的根源,但是 std::unordered_map::try_emplace 为 value(mapped) 类型的构造函数获取键和变量参数,但是 ::emplace 应该采用 std::p air<key, value>:

此外,try_emplace 将键和参数处理为 mapped_type单独,不像 emplace,它需要参数 构造value_type(即 std::p air)。

好吧,这很令人困惑。为什么函数签名是:

template< class... Args >
std::pair<iterator, bool> emplace( Args&&... args );

如上所述,如果需要构造 std::p air<key, value>为什么还要有多个参数?

C++ 模板 Unordered-Map Emplace

评论

2赞 StoryTeller - Unslander Monica 9/23/2023
所有容器上的所有重载都构造了一个,在 maps 的情况下,自 98 年以来已经是一对了。这只是过度的设计原则。他们确实注意到它不太适合地图,因为它过载了。然后在野外测试后应运而生。重要的是要记住,此规范会随着时间的推移而发展,而不是完全提前设计的。emplaceContainer::value_typestd::pairstd::piecewise_constructtry_emplaceemplace
0赞 alfC 9/23/2023
是的,我的理解是,对于关联容器,emplace模式充其量只是误导。这有详细的原因。我的经验法则是,如果程序需要构造以反对只是为了查看在哪里以及是否插入它,这可能意味着这样做不会有太大的收获。
0赞 user4581301 9/23/2023
当你提前设计一切时,最好记住 A) 没有计划在与敌人的接触中幸存下来,B) 敌人就是我们。

答:

1赞 alfC 9/23/2023 #1

通常,这是因为它实际上旨在将参数转发到对象的构造函数。 (它可以是复制构造函数,在这种情况下,您只使用一个参数和值(或要存储的 r-value0,但不需要)。emplace

在这个特定的例子中,它的值是,所以它不太有用。std::pair

例:

#include<unordered_map>

int main() {
    std::unordered_map<int, double> m;
    m.emplace(42, 3.14);  // the constructor of pair will be called with these two parameters.
}

https://godbolt.org/z/PYx51jT4x

有些人不建议对某些容器使用 emplace,因为确实没有太多的收益,并且可能会产生误导,因为很多时候,在知道需要“在哪里”之前,无论如何都会构造值,然后复制到那里,而不是真正放置。

看看 Meyer 的书,“有效的现代 C++:改进 C++11 和 C++ 使用的 42 种具体方法”

现在不确定是否是其中之一。unordered_set

评论

0赞 Zebrafish 9/23/2023
什么对象?std::p air<key、value>还是 value?
0赞 alfC 9/23/2023
@Zebrafish,好点,,这使得成语的用处降低,在这种情况下甚至可能不是对插入的优化。看我的编辑。pair
0赞 user17732522 9/23/2023
"因为真的没有太多的收益,而且可能会产生误导“:当元素类型不可移动或移动成本高昂时,这一点很重要。我真的看不出与其他容器有太大区别。“然后复制到那里,而不是真正放置”:这是不允许的。 只需要 Cpp17EmplaceConstructible。emplace
0赞 user17732522 9/23/2023
此外,构造函数也采用零、一和三个参数。他们也需要得到支持。仅仅为此编写多个重载会很奇怪。std::pairemplace
0赞 alfC 9/23/2023
@user17732522,Cpp17EmplaceConstructible,对于值类型还是整个对?如果将除第一个元素之外的所有参数都转发给构造函数,则情况会有所不同。它是这样做的吗?
0赞 Jan Schultke 9/23/2023 #2

std::unordered_map::emplace完美地将所有参数转发给 的构造函数,因此就好像您调用了:std::pair

// value_type is defined as std::pair<...> for a std::unordered_map
value_type(std::forward<Args>(args...)

通常,将所有参数转发到标准库容器。.emplacevalue_type

对于 ,这开辟了无数的可能性,因为现在您可以:std::unordered_map

  • 调用一元构造函数,如.emplace(std::pair{key, value})
  • 调用二进制构造函数,例如.emplace(key, value)
  • 调用其他构造函数,例如.emplace(std::piecewise_construct, std::forward_as_tuple(key_args...), std::forward_as_tuple(value_args...))

std::unordered_map::try_emplace是不同的,因为它构造了一个与值分开的键。如果密钥尚不存在,它只会构造存储在映射中的完整内容。显然,它不能简单地将所有参数转储到 a 中,并且需要一些分离。std::pairstd::pair


注意:对于大多数意图和目的,try_emplace 优先于放置。另请参阅此答案

注意:这里所说的一切都同样适用于 std::map

1赞 user17732522 9/23/2023 #3

对于所有容器,都接受参数并将它们转发给 的构造函数,该构造函数是 的键和映射类型。emplacevalue_typestd::pairconststd::unordered_map

std::pair具有接受 0、1、2 和 3 参数的构造函数。所以应该支持所有这些。的确,没有构造函数允许在标准库中指定超过 3 个参数,但指定此特定参数最多只接受 3 个参数不会有太大好处。emplacestd::pairemplace

此外,允许用户专用于自己的类型(只要它满足标准库要求),并且可以添加一个接受 3 个以上参数的构造函数。在这种情况下,不应不必要地不相容。std::pairemplace

下面是一个示例,其中将元素插入到映射中的唯一可能方法是使用三个参数 ,因为键类型是不可移动的,使用其构造函数可以避免键或映射类型的所有副本/移动。emplacestd::piecewise_constructstd::pair

struct Key {
    int i;
    Key(int i) : i(i) {}
    Key(const Key&) = delete;
    Key& operator=(const Key&) = delete;
    bool operator==(const Key&) const = default;
};

constexpr auto hash = [](const Key& k){ return k.i; };

int main() {
    std::unordered_map<Key, int, decltype(hash)> m;
    m.emplace(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(1));
}