提问人:David Stone 提问时间:6/5/2012 最后编辑:ildjarnDavid Stone 更新时间:8/15/2022 访问量:88009
为什么我会用 push_back 而不是 emplace_back?
Why would I ever use push_back instead of emplace_back?
问:
C++ 11 向量具有新函数 。与 不同,它依赖于编译器优化来避免复制,它使用完美转发将参数直接发送到构造函数以就地创建对象。在我看来,它做了所有可以做的事情,但有时它会做得更好(但永远不会更糟)。emplace_back
push_back
emplace_back
emplace_back
push_back
我必须使用什么理由?push_back
答:
向后兼容 C++ 之前的编译器。
评论
push_back
emplace_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 });
评论
{}
{}
emplace_back
{}
aggregate
{}
emplace_back
emplace
emplace_back的某些库实现的行为不符合 C++ 标准中的规定,包括 Visual Studio 2012、2013 和 2015 附带的版本。
为了适应已知的编译器错误,最好使用 if 参数引用迭代器或其他对象,这些对象在调用后将无效。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。这是因为第 2 次调用会导致调整大小,此时该点将无效。emplace_back
v[0]
上述代码的工作实现将使用,而不是如下所示:push_back()
emplace_back()
std::vector<int> v;
v.emplace_back(123);
v.push_back(v[0]);
注意:使用整数向量用于演示目的。我在一个更复杂的类中发现了这个问题,该类包括动态分配的成员变量,并且调用导致硬崩溃。emplace_back()
评论
在过去的四年里,我一直在思考这个问题。我得出的结论是,大多数关于 vs. 错过了全貌。push_back
emplace_back
去年,我在C++Now上做了一个关于C++14中的类型演绎的演讲。我开始谈论 vs. 在13:49,但在此之前有一些有用的信息提供了一些支持证据。push_back
emplace_back
真正的主要区别在于隐式构造函数与显式构造函数。考虑以下情况:我们有一个要传递给 或 的参数。push_back
emplace_back
std::vector<T> v;
v.push_back(x);
v.emplace_back(x);
在优化编译器掌握这一点后,这两个语句在生成的代码方面没有区别。传统智慧是,将构建一个临时对象,然后将其移动到其中,而将转发参数并直接将其构建到位,而无需复制或移动。根据标准库中编写的代码,这可能是正确的,但它错误地假设优化编译器的工作是生成您编写的代码。优化编译器的工作实际上是生成代码,如果你是特定于平台的优化专家,并且不关心可维护性,只关心性能,你会编写代码。push_back
v
emplace_back
这两个语句之间的实际区别在于,更强大的人会调用任何类型的构造函数,而更谨慎的人只会调用隐式的构造函数。隐式构造函数应该是安全的。如果你能隐式地从 a 构造一个 ,你就说它可以毫无损失地保存所有信息。在几乎任何情况下,通过一个都是安全的,如果你把它变成一个,没有人会介意。隐式构造函数的一个很好的例子是从 转换为 。隐式转换的一个坏例子是 。emplace_back
push_back
U
T
U
T
T
U
std::uint32_t
std::uint64_t
double
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
这不仅仅是发明的代码。这是我遇到的一个真正的生产错误。代码是 ,但它拥有内容。作为迁移到 C++11 的一部分,我正确地更改为 以指示向量拥有其内存。然而,我在 2012 年根据我的理解进行了这些更改,在此期间我想“什么都可以做,而且更多,所以我为什么要使用?”,所以我也把 改成了 .std::vector<T *>
T *
std::unique_ptr<T>
emplace_back
push_back
push_back
push_back
emplace_back
如果我让代码使用更安全的代码,我会立即发现这个长期存在的错误,并且会被视为升级到 C++11 的成功。相反,我掩盖了这个错误,直到几个月后才发现它。push_back
评论
std::unique_ptr<T>
T *
emplace_back
v
delete
new
explicit
explicit
std::unique_ptr
T *
std::unique_ptr
emplace_back
push_back
考虑使用 c++-17 编译器在 Visual Studio 2019 中发生的情况。我们在一个设置了适当参数的函数中emplace_back。然后有人更改emplace_back调用的构造器的参数。VS 中没有警告 whatsover,代码也编译良好,然后在运行时崩溃。在此之后,我从代码库中删除了所有emplace_back。
评论
仅用于基元/内置类型或原始指针。否则使用 .push_back
emplace_back
评论