32
#include <set>
#include <string>
#include <cassert>

using namespace std::literals;

int main()
{
    auto coll = std::set{ "hello"s };
    auto s = "hello"s;
    coll.insert(std::move(s));
    assert("hello"s == s); // Always OK?
}

C++ 标准是否保证插入关联容器失败不会修改右值引用参数?

4

3 回答 3

26

明确和明确的NO。标准没有这个保证,这就是try_emplace存在的原因。

见注释:

与 insert 或 emplace 不同,如果插入没有发生,这些函数不会从右值参数移动,这使得操作值是仅移动类型的映射变得容易,例如 std::map<std::string, std::unique_ptr<foo>>. 此外,try_emplace 将 mapped_type 的键和参数分开处理,不像 emplace,它需要参数来构造 a value_type(即 a std::pair

于 2019-08-15T15:32:20.967 回答
8

不。

虽然@NathanOliver指出当且仅当没有等效键时才会插入元素,但它不能保证不会修改参数。

实际上,[map.modifiers] 是这样说的

template <class P>
pair<iterator, bool> insert(P&& x);

相当于return emplace(std::forward<P>(x)).

Whereemplace可以完美地转发参数以构造另一个Px处于某种有效但不确定的状态。

这是一个示例,它还演示(不证明)使用std::map(关联容器),值会移动一点:

#include <iostream>
#include <utility>
#include <string>
#include <map>

struct my_class
{
    my_class() = default;
    my_class(my_class&& other)
    {
        std::cout << "move constructing my_class\n";
        val = other.val;
    }
    my_class(const my_class& other)
    {
        std::cout << "copy constructing my_class\n";
        val = other.val;
    }
    my_class& operator=(const my_class& other)
    {
        std::cout << "copy assigning my_class\n";
        val = other.val;
        return *this;
    }
    my_class& operator=(my_class& other)
    {
        std::cout << "move assigning my_class\n";
        val = other.val;
        return *this;
    }
    bool operator<(const my_class& other) const
    {
        return val < other.val;
    }
    int val = 0;
};

int main()
{
    std::map<my_class, int> my_map;
    my_class a;
    my_map[a] = 1;
    std::pair<my_class, int> b = std::make_pair(my_class{}, 2);
    my_map.insert(std::move(b)); // will print that the move ctor was called
}
于 2019-08-15T15:29:16.453 回答
4

(仅针对 C++17 的答案)

我相信正确的答案介于 NathanOliver(现已删除)的答案和 AndyG 的答案之间。

正如 AndyG 所指出的,这样的保证通常不存在:有时,库必须实际执行移动构造,以确定插入是否可以发生。这将是emplace函数的情况,其行为由标准指定为:

效果:插入一个由当且仅当容器中没有与 key 等效的元素的元素时构造的value_type对象。tstd::forward<Args>(args)...t

我们可以将其解释为,t无论如何构造对象,然后如果由于值tt.first已分别存在于集合或映射中而无法发生插入,则将其处理掉。正如AndyG指出的那样,由于方法template <class P> pair<iterator, bool> insert(P&&)std::map根据 来指定的,因此它具有相同的行为。emplace正如 SergeyA 指出的那样,这些try_emplace方法旨在避免这个问题。

但是,在 OP 给出的具体示例中,插入的值与容器的值类型完全相同。这种insert调用的行为由 NathanOliver 先前给出的一般要求段落指定:

效果:t当且仅当容器中没有与 的键等效的元素时才插入t

在这种情况下,在没有发生插入的情况下,没有为库提供修改参数的许可。我相信,除了标准明确允许的之外,调用库函数不应该有任何可观察到的副作用。因此,这种情况下,t不得修改。

于 2019-08-15T15:55:01.760 回答