简体   繁体   English

C ++标准是否保证插入关联容器失败不会修改rvalue-reference参数?

[英]Does the C++ standard guarantee that a failed insertion into an associative container will not modify the rvalue-reference argument?

#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?
}

Does the C++ standard guarantee that a failed insertion into an associative container will not modify the rvalue-reference argument? C ++标准是否保证插入关联容器失败不会修改rvalue-reference参数?

Explicit and unequivocal NO . 明确明确的NO Standard doesn't have this guarantee, and this is why try_emplace exists. Standard没有这种保证,这就是为什么try_emplace存在的原因。

See notes: 请参阅注释:

Unlike insert or emplace, these functions do not move from rvalue arguments if the insertion does not happen , which makes it easy to manipulate maps whose values are move-only types, such as std::map<std::string, std::unique_ptr<foo>> . 与insert或emplace不同, 如果不执行插入操作这些函数就不会从rvalue参数中移出 ,这使得操作值仅为移动类型的std::map<std::string, std::unique_ptr<foo>> (如std::map<std::string, std::unique_ptr<foo>>变得容易。 std::map<std::string, std::unique_ptr<foo>> In addition, try_emplace treats the key and the arguments to the mapped_type separately, unlike emplace , which requires the arguments to construct a value_type (that is, a std::pair ) 此外, try_emplace对待键和单独的参数给mapped_type,不像emplace ,这需要的参数来构建value_type (即,一个std::pair

No. 没有。

While @NathanOliver points out that an element will not be inserted if and only if there is no equivalent key, it does not guarantee that the arguments will not be modified. 虽然@NathanOliver指出,当且仅当没有等效键时,才会插入元素,但这不能保证不会修改参数。

In fact, [map.modifiers] says the following 实际上,[map.modifiers]表示以下内容

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

equivalent to return emplace(std::forward<P>(x)). 等效于return emplace(std::forward<P>(x)).

Where emplace may perfectly forward the arguments to construct another P , leaving x in some valid but indeterminate state. 其中emplace可以完美地转发参数以构造另一个P ,从而使x处于某些有效但不确定的状态。

Here's an example that also demonstrates (not proves) that with std::map (an associative container), a value gets moved around a bit: 这是一个示例,该示例还演示(未证明)使用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
}

(Answer for C++17 only) (仅适用于C ++ 17)

I believe that the correct answer is somewhere in between NathanOliver's (now deleted) answer and AndyG's answer. 我相信正确的答案介于NathanOliver(现已删除)的答案和AndyG的答案之间。

As AndyG points out, such a guarantee cannot exist in general: sometimes, the library must actually perform a move construction just to determine whether or not the insertion can take place. 正如AndyG指出的那样,这种保证通常是不存在的:有时,库实际上必须执行一次移动构造,只是为了确定是否可以进行插入。 This will be the case for the emplace function, whose behaviour is specified by the standard as: emplace函数就是这种情况,其行为由标准指定为:

Effects: Inserts a value_type object t constructed with std::forward<Args>(args)... if and only if there is no element in the container with key equivalent to the key of t . 效果:将一个value_type对象t与构造std::forward<Args>(args)...当且仅当存在与等价键的键容器中没有元件t

We can interpret this as saying that the object t is constructed no matter what, and then is disposed of if the insertion cannot happen because the value t or t.first already exists in the set or map, respectively. 我们可以这样解释,即无论如何构造对象t ,如果因为值tt.first已分别存在于集合或映射中而无法插入,则将其丢弃。 And since the method template <class P> pair<iterator, bool> insert(P&&) of std::map is specified in terms of emplace , as AndyG points out, it has the same behaviour. 并且由于该方法template <class P> pair<iterator, bool> insert(P&&)std::map中的形式指定emplace ,如AndyG指出的那样,它具有相同的行为。 As SergeyA points out, the try_emplace methods are designed to avoid this issue. 正如SergeyA指出的那样, try_emplace方法旨在避免此问题。

However, in the specific example given by the OP, the value being inserted is of exactly the same type as the container's value type . 但是,在OP给出的特定示例中,要插入的值与容器的value type完全相同 The behaviour of such an insert call is specified by the general requirements paragraph previously given by NathanOliver: 此类insert调用的行为由NathanOliver先前给出的一般要求段落指定:

Effects: Inserts t if and only if there is no element in the container with key equivalent to the key of t . 效果:插入t当且仅当存在与等价键的键容器中没有元件t

In this case, there no license is given for the library to modify the argument in the case where the insertion does not take place. 在这种情况下,如果不执行插入操作,则不会为库提供许可证来修改参数。 I believe that calling a library function is not supposed to have any observable side effects besides what the standard explicitly allows. 我相信,除了标准明确允许的功能外,调用库函数不应具有任何可观察到的副作用。 Thus, this case, t must not be modified. 因此,在这种情况下,不得修改t

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM