c++ 11向量有了新函数emplace_back。与依赖编译器优化来避免复制的push_back不同,emplace_back使用完全转发将参数直接发送给构造函数以就地创建对象。在我看来,emplace_back做了所有push_back能做的事情,但有时它会做得更好(但不会更差)。
我使用push_back的原因是什么?
c++ 11向量有了新函数emplace_back。与依赖编译器优化来避免复制的push_back不同,emplace_back使用完全转发将参数直接发送给构造函数以就地创建对象。在我看来,emplace_back做了所有push_back能做的事情,但有时它会做得更好(但不会更差)。
我使用push_back的原因是什么?
当前回答
在过去的四年里,我经常思考这个问题。我得出的结论是,大多数关于push_back和emplace_back的解释都没有看到全貌。
去年,我在c++ Now上做了一个关于c++ 14中的Type Deduction的演讲。我在13:49开始讨论push_back和emplace_back,但在此之前有一些有用的信息提供了一些支持证据。
真正主要的区别在于隐式构造函数和显式构造函数。考虑这样一种情况,我们有一个参数,希望传递给push_back或emplace_back。
std::vector<T> v;
v.push_back(x);
v.emplace_back(x);
After your optimizing compiler gets its hands on this, there is no difference between these two statements in terms of generated code. The traditional wisdom is that push_back will construct a temporary object, which will then get moved into v whereas emplace_back will forward the argument along and construct it directly in place with no copies or moves. This may be true based on the code as written in standard libraries, but it makes the mistaken assumption that the optimizing compiler's job is to generate the code you wrote. The optimizing compiler's job is actually to generate the code you would have written if you were an expert on platform-specific optimizations and did not care about maintainability, just performance.
The actual difference between these two statements is that the more powerful emplace_back will call any type of constructor out there, whereas the more cautious push_back will call only constructors that are implicit. Implicit constructors are supposed to be safe. If you can implicitly construct a U from a T, you are saying that U can hold all of the information in T with no loss. It is safe in pretty much any situation to pass a T and no one will mind if you make it a U instead. A good example of an implicit constructor is the conversion from std::uint32_t to std::uint64_t. A bad example of an implicit conversion is double to std::uint8_t.
我们希望在编程时保持谨慎。我们不想使用功能强大的功能,因为功能越强大,就越容易意外地做一些不正确或意想不到的事情。如果打算调用显式构造函数,则需要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,我就会立即发现这个长期存在的错误,并且它将被视为升级到c++ 11的成功。相反,我掩盖了这个漏洞,直到几个月后才发现它。
其他回答
push_back总是允许使用统一初始化,这是我非常喜欢的。例如:
struct aggregate {
int foo;
int bar;
};
std::vector<aggregate> v;
v.push_back({ 42, 121 });
另一方面,v.emplace_back({42,121});不会起作用。
向后兼容c++ 11之前的编译器。
emplace_back的一些库实现行为不符合c++标准(包括Visual Studio 2012、2013和2015发布的版本)中指定的行为。
为了适应已知的编译器错误,如果参数引用迭代器或其他在调用后无效的对象,最好使用std::vector::push_back()。
std::vector<int> v;
v.emplace_back(123);
v.emplace_back(v[0]); // Produces incorrect results in some compilers
在一个编译器上,v包含值123和21,而不是预期的123和123。这是因为对emplace_back的第二次调用导致了大小的调整,此时v[0]无效。
上述代码的工作实现将使用push_back()而不是emplace_back(),如下所示:
std::vector<int> v;
v.emplace_back(123);
v.push_back(v[0]);
注意:使用int类型的vector仅用于演示目的。我在一个更复杂的类中发现了这个问题,这个类包括动态分配的成员变量,对emplace_back()的调用导致了硬崩溃。
考虑使用c++-17编译器的Visual Studio 2019会发生什么。我们在函数中有emplace_back,并设置了适当的参数。然后有人更改emplace_back调用的构造函数的参数。在VS中没有任何警告,代码编译也很好,然后在运行时崩溃。在此之后,我从代码库中删除了所有emplace_back。
在过去的四年里,我经常思考这个问题。我得出的结论是,大多数关于push_back和emplace_back的解释都没有看到全貌。
去年,我在c++ Now上做了一个关于c++ 14中的Type Deduction的演讲。我在13:49开始讨论push_back和emplace_back,但在此之前有一些有用的信息提供了一些支持证据。
真正主要的区别在于隐式构造函数和显式构造函数。考虑这样一种情况,我们有一个参数,希望传递给push_back或emplace_back。
std::vector<T> v;
v.push_back(x);
v.emplace_back(x);
After your optimizing compiler gets its hands on this, there is no difference between these two statements in terms of generated code. The traditional wisdom is that push_back will construct a temporary object, which will then get moved into v whereas emplace_back will forward the argument along and construct it directly in place with no copies or moves. This may be true based on the code as written in standard libraries, but it makes the mistaken assumption that the optimizing compiler's job is to generate the code you wrote. The optimizing compiler's job is actually to generate the code you would have written if you were an expert on platform-specific optimizations and did not care about maintainability, just performance.
The actual difference between these two statements is that the more powerful emplace_back will call any type of constructor out there, whereas the more cautious push_back will call only constructors that are implicit. Implicit constructors are supposed to be safe. If you can implicitly construct a U from a T, you are saying that U can hold all of the information in T with no loss. It is safe in pretty much any situation to pass a T and no one will mind if you make it a U instead. A good example of an implicit constructor is the conversion from std::uint32_t to std::uint64_t. A bad example of an implicit conversion is double to std::uint8_t.
我们希望在编程时保持谨慎。我们不想使用功能强大的功能,因为功能越强大,就越容易意外地做一些不正确或意想不到的事情。如果打算调用显式构造函数,则需要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,我就会立即发现这个长期存在的错误,并且它将被视为升级到c++ 11的成功。相反,我掩盖了这个漏洞,直到几个月后才发现它。