std :: unordered_set <t> :: insert(T&amp;&amp;):如果存在则移动参数</t>

时间:2012-04-06 12:49:32

标签: c++ c++11

这个问题是关于C ++ 11标准库中几个函数的规范,它们将它们的参数作为右值引用,但不要在所有情况下都使用它们。一个例子是 std::unordered_set<T>::insert(T&&)

很明显,这个方法将使用T的移动构造函数来构造容器中的元素(如果它尚不存在)。但是,如果元素已存在于容器中会发生什么?我很确定在这种情况下没有理由改变对象。但是,我没有在C ++ 11标准中找到任何支持我声明的内容。

这是一个示例,说明为什么这可能很有趣。以下代码从std :: cin读取行并删除第一次出现的重复行。

std::unordered_set<std::string> seen;
std::string line;
while (getline(std::cin, line)) {
    bool inserted = seen.insert(std::move(line)).second;
    if (!inserted) {
        /* Is it safe to use line here, i.e. can I assume that the
         * insert operation hasn't changed the string object, because 
         * the string already exists, so there is no need to consume it. */
        std::cout << line << '\n';
    }
}

显然,这个例子适用于GCC 4.7。但我不确定,根据标准是否正确。

3 个答案:

答案 0 :(得分:6)

我在标准(17.4.6.9)中找到了这个注释:

  

[注意:如果程序在将左值传递给库函数时将左值转换为x值(例如通过调用带有参数move(x)的函数),则程序是有效的要求该函数将该左值作为临时值处理。实现可以自由地优化别名检查,如果参数是左值,则可能需要这些检查。 - 结束说明]

虽然它没有直接回答你的问题,但它确实表明你已经有效地“给”了库函数的参数作为临时函数,所以一旦你调用{{1}我就不会依赖它的值}。据我所知,库实现有权从参数中移出,即使它随后确定它不会将值保留在容器中。

答案 1 :(得分:1)

在§23.2.5/表103中对无序关联容器的insert给出的语义没有指定如果插入失败,是否调用insert的参数的移动构造函数,措辞只讨论插入是否发生:

  

<强> a_uniq.insert(t)

     

返回:pair<iterator, bool>

     

要求:如果t是非常量   rvalue表达式,T应为MoveInsertable到X中;否则,T   应该是CopyInsertable到X

     

效果:当且仅当时插入t   容器中没有元素,其键等价于键   t。返回对的bool组件表示是否   插入发生,迭代器组件指向   具有等效于t的键的键的元素。

但是,emplace的规范更清晰(也来自表103),您应该能够使用它而不是insert来获得所需的保证:

< S>
  

<强> a_uniq.emplace(args)

     

返回:pair<iterator, bool>

     

要求:T应为   Emplace可以从argsConstructible到X

     

效果:插入T对象t   用std :: forward(args)... 构造,当且仅当有   容器中没有元素,其键等效于t 的键。该   当且仅当返回时,返回对的bool组件才为真   插入发生,并且该对的迭代器组件指向   使用等效于t键的键的元素。

我将此解释为((插入一个T对象t构造...)(当且仅当容器中没有元素...)), ie 只有在容器中已有匹配元素时才会发生插入和构造。如果没有构造对象,则传入的std::string将永远不会传递给移动构造函数,因此在失败的emplace调用之后仍然有效。

gcc 4.7.0似乎不支持unordered_set::emplace,但它符合标准(§23.5.6.1)

正如@NicolBolas在评论中指出的那样,尽管如上所述,如果已存在冲突条目,则无法实现不构造emplace的{​​{1}}函数。

因此,以符合标准的方式获取所需语义的唯一方法是执行T,然后有条件地执行findinsert。< / p>

答案 2 :(得分:0)

这是对的。

当然 - 在处理哲学时 - 任何事都可以质疑,但编译器必须以某种方式做。

设计师的选择是 - 执行移动 - 必须有的地方。 如果没有这样的地方,那么移动就不会发生。

请注意,无论函数采用&amp;&amp;,它都假定参数是“临时的”:如果它没有“窃取”数据,临时将在表达式结束时被销毁。 如果临时性强制(通过std :: move),对象将保留在任何情况下,直到被自己的范围销毁。有或没有原始数据。