【问题标题】:c++11 emplace_back and push_back syntax with structc++11 emplace_back 和 push_back 语法与结构
【发布时间】:2014-01-03 09:30:16
【问题描述】:

我正在使用 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],为什么这不像在 push 情况下那样工作?
对于[e],为什么加了类名就可以了?
对于 [h],如果有一个将参数映射到成员的构造函数,这似乎是最有效的代码
对于 [j],这似乎和 push_back 一样糟糕,而且我不知道为什么有人应该在 push_back 上这样做
对于 [k,m],添加了一个移动构造函数,似乎正在调用 push_back(T&amp;&amp;),这导致与 emplace 相同的性能。但同样,由于额外的输入,我不确定为什么有人会这样做。

我读到 MSVC 没有为您添加移动构造函数: Why is copy constructor called in call to std::vector::emplace_back()?

[d,e] 有什么区别,emplace 为何挑剔。为什么push_back(T&amp;&amp;) 在不添加结构名称的情况下工作?

如果我知道有一个将每个成员作为参数的构造函数,我只能获得 emplace 的全部好处?

我应该坚持使用push_back 吗?是否有任何理由使用emplace_back(structname{1,2,3}) 而不是push_back({1,2,3}),因为它最终会调用push_back(T&amp;&amp;),并且更容易输入?

第三,emplace_back(arg1,arg2,etc),如何施展魔法来完全避免复制或移动构造函数?

【问题讨论】:

  • 作为快速评论,emplace_back({42, 42} 通常不起作用,因为它变成了两个整数的 initializer-list,而不是 my_pair 结构。如果您还添加了 initializer list 构造函数,我认为 emplace_back 调用可能会起作用。当然,有人会认为这会直接解决问题,但是您还必须考虑 emplace_back 的完美转发语义:在大多数情况下,除非您明确命名类型,否则像 emplace_back 这样的完美转发函数将采取任何完全按面值给出,没有额外的考虑或转换

标签: c++ c++11 rvalue-reference


【解决方案1】:

对于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

操作语义:
将使用 std::forward&lt;Args&gt;(args)... 构造的 T 类型的对象附加到 @987654331。

要求: T 应 EmplaceConstructible 到 @987654331 @来自args。对于vector,T 也应该是 MoveInsertable 到 X 中的。

对于一个类型是 EmplaceConstructible,

§ 23.2.1.13

——T is EmplaceConstructible into X from args,对于零个或多个参数args,意味着下面的表达式是良构的:

allocator_traits&lt;A&gt;::construct(m, p, args);

std::allocator_traits::construct() 反过来(如果可能的话)a.construct(p, std::forward&lt;Args&gt;(args)...)(其中aEmplaceConstructible 表达式中是m)。

a.construct() 这里是std::allocator::construct(),它调用::new((void *)p) U(std::forward&lt;Args&gt;(args)...)这是导致编译错误的原因。

U(std::forward&lt;Args&gt;(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's signaturespush_back()的参数没有被推导出来,这意味着通过push_back({1, 2}),一个具有向量元素类型类型的临时对象首先被创建并使用{1, 2}初始化。然后,该临时对象将成为传递给 push_back(T&amp;&amp;) 的对象。


我应该坚持使用 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})一样。

【讨论】:

  • 我不同意您在答案最后一段中的“不能在不复制的情况下插入聚合”语句。您始终可以执行以下操作: v.emplace_back(); v.back().foo = 41; v.back().bar = 42;
  • @PowerGamer 也就是说,如果聚合是默认可构造的。但是,是的,这是一个很好的补充。 (直到现在我才真正意识到你已经指出了这一点,谢谢)
猜你喜欢
  • 2015-01-07
  • 2021-10-12
  • 2011-05-17
  • 2018-11-15
  • 1970-01-01
  • 1970-01-01
  • 2020-09-12
  • 2019-05-22
相关资源
最近更新 更多