这个问题是关于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。但我不确定,根据标准是否正确。
答案 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
来获得所需的保证:
<强>
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
,然后有条件地执行find
或insert
。< / p>
答案 2 :(得分:0)
这是对的。
当然 - 在处理哲学时 - 任何事都可以质疑,但编译器必须以某种方式做。
设计师的选择是 - 执行移动 - 必须有去的地方。 如果没有这样的地方,那么移动就不会发生。
请注意,无论函数采用&amp;&amp;,它都假定参数是“临时的”:如果它没有“窃取”数据,临时将在表达式结束时被销毁。 如果临时性强制(通过std :: move),对象将保留在任何情况下,直到被自己的范围销毁。有或没有原始数据。