我正在使用MSVC,Visual Studio 2013。
假设我有一个结构:
struct my_pair {
int foo, bar;
};
我想有效地添加一堆这些,而不是创建一个临时的,然后丢弃它:
vector<my_pair> v;
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没有为您添加移动构造函数: Why is copy constructor called in call to 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)
如何完全避免复制或移动构造函数?
答案 0 :(得分:12)
对于v.emplace_back({41,42});
,请参阅how to use std::vector::emplace_back for vector<vector<int> >?
v.emplace_back(41,42);
由于标准中的某些规则(某些强调我的规则)不起作用:
表101 - 可选的序列容器操作
表达式:
a.emplace_back(args)
返回类型:
void
操作语义:
追加由T
构造的std::forward<Args>(args)...
类型的对象。
要求: T应从<{1}} EmplaceConstructible 进入X
。对于args
,T也应该是MoveInsertable到X。
对于 vector
的类型,
§23.2.1.13
- 来自args的 EmplaceConstructible 进入 X ,对于零个或多个参数args,意味着以下表达式格式正确:
EmplaceConstructible
std::allocator_traits::construct()
反过来(如果可能的话)allocator_traits<A>::construct(m, p, args);
({em> EmplaceConstructible 表达式中a.construct(p, std::forward<Args>(args)...)
为a
)。
m
这里是std::allocator::construct()
,调用a.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
的过程完全相同,只是参数是一个r值v.emplace_back(41,42);
。
附加1:
为什么push_back(T&amp;&amp;)在没有添加结构名称的情况下工作?
这是因为push_back
's signatures。不推导出my_pair
的参数,这意味着通过执行push_back()
,首先创建一个具有向量元素类型类型的临时对象,并使用push_back({1, 2})
进行初始化。那个临时对象将是传递给{1, 2}
的临时对象。
我应该坚持使用push_back吗?有没有理由使用emplace_back(structname {1,2,3})而不是push_back({1,2,3}),因为它最终会调用push_back(T&amp;&amp;),并且更容易输入?
基本上,push_back(T&&)
函数用于优化和消除在插入时创建临时文件和复制或移动构造对象的成本。但是,对于聚合数据类型的情况,不可能执行类似emplace*
的操作,并且可以插入它们的唯一方法是创建临时然后复制或移动,然后通过所有方法更喜欢更精简的语法,并转到emplace_back(1, 2, 3)
,它基本上具有与push_back({1,2,3})
相同的效果。