【发布时间】:2014-03-31 13:51:27
【问题描述】:
我可以通过将std::vector::push_back 替换为emplace_back 并使用C++ 11 编译器编译它来破坏有效的C++03 程序吗?通过阅读emplace_back 参考,我认为这不应该发生,但我承认我没有完全得到右值参考。
【问题讨论】:
我可以通过将std::vector::push_back 替换为emplace_back 并使用C++ 11 编译器编译它来破坏有效的C++03 程序吗?通过阅读emplace_back 参考,我认为这不应该发生,但我承认我没有完全得到右值参考。
【问题讨论】:
我构造了一个简短的示例,当push_back 被emplace_back 替换时实际上无法编译:
#include <vector>
struct S {
S(double) {}
private:
explicit S(int) {}
};
int main() {
std::vector<S>().push_back(0); // OK
std::vector<S>().emplace_back(0); // error!
}
对push_back 的调用需要将其参数0 从类型int 转换为类型S。由于这是一个隐式转换,所以不考虑显式构造函数S::S(int),而是调用S::S(double)。另一方面,emplace_back 执行直接初始化,因此S::S(double) 和S::S(int) 都被考虑在内。后者是更好的匹配,但它是private,所以程序格式不正确。
【讨论】:
emplace_back 可以调用显式构造函数”。然后我愚蠢地想,“但是当从emplace_back 移动到push_back 时,这是一个突破性的变化,而不是相反”。您可以在emplace_back 调用变得模棱两可而push_back 不是struct S { S(double) {} explicit S(float) {} }; 的情况下做同样的事情,但当然这是一组可怕 构造函数给S。
是的,您可以更改行为(不仅仅是避免复制构造函数调用),因为emplace_back 只能看到不完美的转发参数。
#include <iostream>
#include <vector>
using namespace std;
struct Arg { Arg( int ) {} };
struct S
{
S( Arg ) { cout << "S(int)" << endl; }
S( void* ) { cout << "S(void*)" << endl; }
};
auto main()
-> int
{
vector<S>().ADD( 0 );
}
示例构建:
[H:\开发\测试\0011] > g++ foo.cpp -D ADD=emplace_back && a S(int) [H:\开发\测试\0011] > g++ foo.cpp -D ADD=push_back && a S(void*) [H:\开发\测试\0011] > _附录:正如 Brian Bi 在 his answer 中指出的那样,另一个可能导致不同行为的区别是 push_back 调用涉及到 T 的隐式转换,这忽略了 @ 987654326@ 构造函数和转换运算符,而emplace_back 使用直接初始化,它也考虑了explicit 构造函数和转换运算符。
【讨论】:
null pointer constant -> void* -> S 是push_back 唯一可用的转换。对于emplace_back,0 是一个空指针常量的事实丢失了,因为模板参数包无法捕获它,它只是捕获它是一个int(因此“不完美地转发”)。此外,不需要在一个序列中进行两个用户定义的转换,因为我们明确地构造了一个S。所以Arg构造函数被选择为S,隐式转换int -> Arg。
emplace 版本在异常情况下根本不会创建所需类型的对象。这可能会导致错误。
考虑以下示例,为简单起见,它使用std::vector(假设uptr 的行为类似于std::unique_ptr,除了构造函数不明确):
std::vector<uptr<T>> vec;
vec.push_back(new T());
它是异常安全的。创建一个临时的uptr<T> 以传递给push_back,该push_back 被移动到向量中。如果向量的重新分配失败,分配的T 仍归智能指针所有,智能指针会正确删除它。
比较:
std::vector<uptr<T>> vec;
vec.emplace_back(new T());
emplace_back 不允许创建临时对象。 ptr 将在向量中就地创建一次。如果重新分配失败,就没有就地创建的位置,也永远不会创建智能指针。 T 将被泄露。
当然,最好的选择是:
std::vector<std::unique_ptr<T>> vec;
vec.push_back(make_unique<T>());
这相当于第一个,但使智能指针的创建显式。
【讨论】:
如果您在向量中保存的对象的复制构造函数中没有疯狂的副作用,那么没有。
emplace_back 被引入以优化不必要的复制和移动。
【讨论】:
push_back。
假设一个用户定义的类可以从花括号初始化器中初始化。例如
struct S {
int value;
};
然后
std::vector<S> v;
v.push_back({0}); // fine
v.emplace_back({0}); // template type deduction fails
std::vector::emplace_back 是模板函数,但 std::vector::push_back 是非模板函数。使用大括号初始化器 std::vector::emplace_back 会失败,因为 template argument deduction 失败。
非推断上下文
6) 参数 P,其 A 是一个花括号初始化列表,但 P 不是
std::initializer_list或对一个的引用:
【讨论】:
int main() {
std::vector<S>().push_back(0);
std::vector<S>().emplace_back(0);
}
在 emplace_back 中给出 struct 构造函数,即上面的代码会是这样的
int main() {
std::vector<S>().push_back(0);
std::vector<S>().emplace_back(S(0));
}
【讨论】: