17

这个问题是关于 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。但我不确定,如果按照标准是正确的。

4

3 回答 3

6

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

[注意:如果程序将左值转换为 xvalue,同时将该左值传递给库函数(例如,通过使用参数调用函数move(x)),则程序实际上是在要求该函数将该左值视为临时值。如果参数是左值,则该实现可以免费优化别名检查,这可能需要。——尾注]

虽然它没有直接回答您的问题,但它确实表明您已经有效地将库函数的参数作为临时“给予”,因此一旦您调用insert. 据我所知,库实现将有权从参数中移动,即使它随后确定它不会将值保留在容器中。

于 2012-04-06T13:05:11.453 回答
1

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

a_uniq.insert(t)

回报:pair<iterator, bool>

要求:如果t是非 const 右值表达式,T则应可 MoveInsertable 到 X 中;否则,T 应 CopyInsertable 到X.

效果:t当且仅当容器中没有与 的键等效的元素时才 插入t。返回 pair 的 bool 分量表示插入是否发生,iterator 分量指向 key 与 的 key 等价的元素t

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

a_uniq.emplace(args)

回报:pair<iterator, bool>

要求:T 应该是 EmplaceConstructible 到Xargs 中的。

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

我将其解释为((插入构造的T对象t...)(当且仅当容器中没有元素...)),插入和构造都应该仅在没有匹配元素的情况下发生容器。如果没有构造对象,则您传入的将永远不会传递给移动构造函数,因此在调用std::string失败后仍然有效。emplace

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

正如@NicolBolas 在评论中指出的那样,尽管有上述内容,但如果冲突条目已经存在,则不可能实现emplace不构造 a 的函数。T

因此,以符合标准的方式获得所需语义的唯一方法是执行 afind后跟有条件的 an insertor emplace

于 2012-04-06T16:25:42.757 回答
0

它是正确的。

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

设计师的选择是——要执行一个动作——必须有一个地方可以。如果没有这样的地方,移动就不会发生。

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

于 2012-04-06T13:02:54.327 回答