为什么我会使用推送

C ++ 11矢量具有新的函数emplace_back 。 与依靠编译器优化来避免副本的push_back不同, emplace_back使用完美转发将参数直接发送到构造函数以便就地创建对象。 在我看来, emplace_back所有push_back都可以做到的,但有些时候它会做得更好(但从不会更糟糕)。

我有什么理由使用push_back


push_back总是允许使用统一的初始化,我非常喜欢它。 例如:

struct aggregate {
    int foo;
    int bar;
};

std::vector<aggregate> v;
v.push_back({ 42, 121 });

另一方面, v.emplace_back({ 42, 121 }); 不管用。


与C ++ 11之前的编译器向后兼容。


在过去的四年里,我已经想到了这个问题。 我得出的结论是,关于push_backemplace_back大多数解释emplace_back未能完整展示。

去年,我在C ++ Now上发表了关于C ++ 14类型扣除的演讲。 我在13:49开始讨论push_backemplace_back ,但有一些有用的信息在此之前提供了一些支持性证据。

真正的主要区别在于隐式和显式构造函数。 考虑我们想要传递给push_backemplace_back的单个参数的情况。

std::vector<T> v;
v.push_back(x);
v.emplace_back(x);

在您的优化编译器掌握了这一点之后,就生成的代码而言,这两个语句之间没有区别。 传统的看法是, push_back将构造一个临时对象,然后它将移动到vemplace_back将转发该参数并直接在没有拷贝或移动的位置构建它。 这可能是基于标准库中编写的代码,但它错误地假设优化编译器的工作是生成您编写的代码。 优化编译器的工作实际上是生成如果您是特定于平台的优化方面的专家并且不关心可维护性(即性能)的情况下编写的代码。

这两个语句之间的实际区别在于更强大的emplace_back将调用任何类型的构造函数,而更谨慎的push_back将只调用隐式的构造函数。 隐含的构造函数应该是安全的。 如果你可以从T隐含地构造一个U ,那么你就是说U可以保存T所有信息而不会丢失。 在几乎任何情况下通过一个T都是安全的,如果你把它变成一个U ,没有人会介意。 隐式构造函数的一个很好的例子是从std::uint32_tstd::uint64_t的转换。 隐式转换的一个不好的例子是std::uint8_t double

我们希望在编程时保持谨慎。 我们不想使用强大的功能,因为功能越强大,越容易意外地做错误或意外的事情。 如果你打算调用显式的构造函数,那么你需要emplace_back的力量。 如果您只想调用隐式构造函数,请坚持push_back的安全性。

一个例子

std::vector<std::unique_ptr<T>> v;
T a;
v.emplace_back(std::addressof(a)); // compiles
v.push_back(std::addressof(a)); // fails to compile

std::unique_ptr<T>具有T *的显式构造函数。 因为emplace_back可以调用显式构造函数,所以传递非拥有指针就可以编译。 但是,当v超出范围时,析构函数将尝试调用该指针上的delete ,而不是由new分配,因为它只是一个堆栈对象。 这导致未定义的行为。

这不仅仅是发明的代码。 这是我遇到的一个真正的生产错误。 代码是std::vector<T *> ,但它拥有内容。 作为迁移到C ++ 11的一部分,我正确地将T *更改为std::unique_ptr<T>以表明该向量拥有它的内存。 不过,我在2012年将这些变化置于了自己的理解之外,在此期间,我认为“emplace_back能够做所有push_back可以做的事情,所以我为什么要使用push_back?”,所以我也将push_back更改为emplace_back

如果我把代码留在使用更安全的push_back ,我会立即发现这个长期存在的bug,并将其视为升级到C ++ 11的成功例子。 相反,我掩盖了这个错误,直到几个月后才发现它。

链接地址: http://www.djcxy.com/p/85705.html

上一篇: Why would I ever use push

下一篇: Resolve build errors due to circular dependency amongst classes