我正在使用MSVC,Visual Studio 2013.
假设我有一个结构:
struct my_pair { int foo, bar; };
我想有效地添加一堆这些,而不是创建一个临时的,然后丢弃它:
vectorv; v.push_back(41, 42); // does not work [a] v.push_back({41,42}); // works [b] v.emplace_back(41,42); // does not work [c] v.emplace_back({41,42}); // does not work [d] v.emplace_back(my_pair{41,42}); //works [e]
现在,如果我将构造函数和复制构造函数添加到我的代码中:
my_pair(int foo_, int bar_) : foo(foo_), bar(bar_) { cout << "in cstor" << endl; } my_pair(const my_pair& copy) : foo(copy.foo), bar(copy.bar) { cout << "in copy cstor" << endl; }
然后行为改变:
v.push_back(41, 42); // does not work [f] v.push_back({41,42}); // displays "in cstor" and "in copy cstor" [g] v.emplace_back(41,42); // displays "in cstor" [h] v.emplace_back({41,42}); // does not work [i] v.emplace_back(my_pair{41,42}); // "in cstor" and "in copy cstor" [j]
如果我添加一个移动构造函数:
my_pair(my_pair&& move_) : foo(move_.foo), bar(move_.bar) { cout << "in move cstor" << endl; }
然后:
v.emplace_back(my_pair{41,42}); //displays "in cstor", "in move cstor" [k] v.emplace_back({41,42}); // still does not work [l] v.push_back({41,42}); // displays "in cstor", "in move cstor" [m]
问题:
对于[a,b]我理解工作和不工作的原因.
对于[c],它不起作用,因为没有构造函数转发参数.
对于[d],为什么这不像推动案例那样起作用?
对于[e],为什么在添加类名时它会起作用?
对于[H] ,好像这是最有效的代码,如果存在该参数映射到成员构造
为[J] ,好像这是一个的push_back坏和额外打字我不知道为什么有人应该通过push_back
为[k,m]执行此操作,添加一个移动构造函数,它似乎push_back(T&&)
被调用,导致与emplace相同的性能.但同样,通过额外打字,我不确定为什么有人会这样做.
我读到MSVC没有为你添加一个移动构造函数: 为什么在调用std :: vector :: emplace_back()时调用了复制构造函数?
[d,e]之间的区别是什么,以及为什么对它有挑衅性.为什么push_back(T&&)
没有添加结构名称就可以工作?
如果我知道有一个构造函数将每个成员作为参数,我只能获得emplace的全部好处吗?
我应该坚持下去push_back
吗?是否有任何理由使用emplace_back(structname{1,2,3})
而不是push_back({1,2,3})
因为它最终会调用push_back(T&&)
,并且更容易键入?
第三,如何完全emplace_back(arg1,arg2,etc)
避免复制或移动构造函数?
对于v.emplace_back({41,42});
,请参阅如何使用std ::矢量:: emplace_back矢量<矢量<int>的>?
v.emplace_back(41,42);
由于标准中的一些规则(一些强调我的),它不起作用:
表101 - 可选的序列容器操作
表达:
a.emplace_back(args)
返回类型:
void
操作语义:
追加T
构造类型的对象std::forward<Args>(args)...
.
要求:牛逼应EmplaceConstructible为X
从args
.因为vector
,T也应该是MoveInsertable到X.
对于一个类型EmplaceConstructible
,
§23.2.1.13
- 对于零个或多个参数args,T是EmplaceConstructible到X的ar,意味着以下表达式格式正确:
allocator_traits<A>::construct(m, p, args);
std::allocator_traits::construct()
反过来做(如果可能)a.construct(p, std::forward<Args>(args)...)
(其中,a
是m
在EmplaceConstructible表达).
a.construct()
这是std::allocator::construct()
,哪个电话::new((void *)p) U(std::forward<Args>(args)...)
.这是导致编译错误的原因.
U(std::forward<Args>(args)...)
(注意使用直接初始化)会找到一个U
接受转发参数的构造函数.但是,在您的情况下,my_pair
是一种聚合类型,只能使用支撑初始化语法(聚合初始化)进行初始化.
v.emplace_back(my_pair{41,42});
因为它调用隐式生成的默认复制构造函数或移动构造函数(请注意,可能并不总是生成这两个).my_pair
首先构造一个临时的,它经历与之相同的过程v.emplace_back(41,42);
,只是参数是一个r值my_pair
.
附加1:
为什么push_back(T &&)在没有添加结构名称的情况下工作?
这是因为push_back
签名.push_back()
这个参数不是推导出来的,这意味着,通过这样做push_back({1, 2})
,首先创建一个具有向量元素类型类型的临时对象并初始化{1, 2}
.那个临时对象将是传递给它的那个push_back(T&&)
.
我应该坚持使用push_back吗?有没有理由使用emplace_back(structname {1,2,3})而不是push_back({1,2,3})因为它最终会调用push_back(T &&),并且更容易输入?
基本上,emplace*
函数用于优化和消除创建临时对象以及在插入对象时复制或移动构造对象的成本.但是,对于聚合数据类型的情况,做emplace_back(1, 2, 3)
不太可能的事情,并且你可以插入它们的唯一方法是通过创建临时然后复制或移动,然后通过所有方式更喜欢更精简的语法,并去push_back({1,2,3})
,在哪里它基本上具有与之相同的性能emplace_back(structname{1,2,3})
.