GCC和VC++中std::vector::emplace_back的区别

8
我听说现代C++的一个建议是在容器中进行添加时使用emplace_back而不是push_backemplace_back接受容器中存储类型的任何构造函数版本的参数)。
根据标准草案N3797 23.3.6.5 (1),它指出:

备注:如果新大小大于旧容量,则会导致重新分配。如果没有发生重新分配,则插入点之前的所有迭代器和引用仍然有效。如果除T的复制构造函数、移动构造函数、赋值运算符或移动赋值运算符以外的操作引发异常,那么没有效果。如果非CopyInsertable T的移动构造函数引发异常,则效果是未指定的。

这说明了当不需要重新分配时会发生什么,但在容器需要增长时问题尚未解决。
在这段代码中:
#include <iostream>
#include <vector>

int main() {
    std::vector<unsigned char> buff {1, 2, 3, 4};
    buff.emplace_back(buff[0]);
    buff.push_back(buff[1]);
    for (const auto& c : buff) {
        std::cout << std::hex << static_cast<long>(c) << ", ";
    }
    std::cout << std::endl;
    return 0;
}

使用VC++ (Visual Studio 2013 Update 4)GCC 4.9.1 (MinGW)在Windows 8.1中以Debug模式编译。

当使用VC++编译时,输出结果为:

1, 2, 3, 4, dd, 2

当使用GCC编译时,输出结果为:
1, 2, 3, 4, 1, 2

检查VC++中emplace_back的实现,不同之处在于代码的前几行会检查容器是否需要增长(如果需要则增长),在容器需要增长的情况下,emplace_back方法中接收到的对第一个元素(buff[0])的引用将失效,并且当在新创建的容器元素中设置值时该值是无效的。
push_back的情况下,因为要追加的元素的创建是在参数绑定中完成的(在容器可能增长之前),所以它能够正常工作。
我的问题是: 当容器需要增长时,如果调用emplace_back并且参数是对同一容器的引用,则此行为是实现定义、未指定或存在编译器实现问题(假设在VC++中,因为GCC的行为更接近期望)

3
这是 http://open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#760 吗? - dyp
1
我想知道为什么以及它如何可能被期望工作。 - n. m.
2
这是我论文的更好视图:http://htmlpreview.github.io/?https://github.com/HowardHinnant/papers/blob/master/insert_vs_emplace.html - Howard Hinnant
1
从相关问题中的@HowardHinnant的评论(https://dev59.com/_2Af5IYBdhLWcg3wMwXX?lq=1#comment38698932_24908718)转载:http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2164,他反对我上面链接的LWG 760的建议解决方案。 - dyp
显示剩余5条评论
1个回答

0
当您使用&operator []时,它返回一个引用。然后您使用了emplace_back导致重新分配内存,从而使所有先前的引用无效。这两个规则都是良好定义的。正确的事情应该发生异常。我实际上希望如果在调试器下运行debug版本,VC++版本会抛出异常。 push_back具有相同的两个规则,这意味着它也将执行相同的操作。我几乎可以肯定,交换两行emplace_back/push_back将导致相同的行为。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接