在emplace()中创建对象时复制省略

时间:2022-07-26 16:38:22

I see a lot of code at work where people use emplace and emplace_back with a temporary object, like this:

我看到很多代码在工作中人们使用emplace和emplace_back与临时对象,如下所示:

struct A {
    A::A(int, int);
};

vector<A> v;
vector<A>.emplace_back(A(1, 2));

I know that the whole point of emplace_back is to be able to pass the parameters directly, like this:

我知道emplace_back的重点是能够直接传递参数,如下所示:

v.emplace_back(1, 2);

But unfortunately this is not clear to a few people. But let's not dwell on that....

但不幸的是,一些人并不清楚这一点。但是,我们不要纠缠于此......

My question is: is the compiler able to optimize this and skip the create and copy? Or should I really try to fix these occurrences?

我的问题是:编译器是否能够对此进行优化并跳过创建和复制?或者我应该真的尝试修复这些事件?

For your reference... we're working with C++14.

供您参考......我们正在使用C ++ 14。

4 个解决方案

#1


13  

My question is: is the compiler able to optimize this and skip the create and copy? Or should I really try to fix these occurrences?

我的问题是:编译器是否能够对此进行优化并跳过创建和复制?或者我应该真的尝试修复这些事件?

It can't avoid a copy, in the general case. Since emplace_back accepts by forwarding references, it must create temporaries from a pure standardese perspective. Those references must bind to objects, after all.

在一般情况下,它无法避免副本。由于emplace_back通过转发引用接受,因此必须从纯粹的标准角度创建临时值。毕竟,这些引用必须绑定到对象。

Copy elision is a set of rules that allows a copy(or move) constructor to be avoided, and a copy elided, even if the constructor and corresponding destructor have side-effects. It applies in only specific circumstances. And passing arguments by reference is not one of those. So for non-trivial types, where the object copies can't be inlined by the as-if rule, the compiler's hands are bound if it aims to be standard conformant.

复制省略是一组规则,允许避免复制(或移动)构造函数,并且复制省略,即使构造函数和相应的析构函数具有副作用。它仅适用于特定情况。通过引用传递参数不是其中之一。因此,对于非平凡类型,其中对象副本不能通过as-if规则内联,如果编译器的目标是符合标准,则编译器的指针是绑定的。

#2


4  

is the compiler able to optimize this and skip the create and copy?

是编译器能够优化这个并跳过创建和复制?

There is not necessarily a copy involved. If a move constructor is available, there will be a move. This cannot be optimized away, as the direct initialization case will just call the init constructor, while in the other case, the move constructor will be called additionally (including its side-effects).

不一定涉及副本。如果移动构造函数可用,则会有移动。这不能被优化掉,因为直接初始化情况只会调用init构造函数,而在另一种情况下,移动构造函数将被另外调用(包括其副作用)。

Therefore, if possible, you should refactor those codes.

因此,如果可能,您应该重构这些代码。

#3


2  

The easy answer is no; elision doesn't work with perfect forwarding. But this is so the answer is actually yes.

简单的答案是否定的; elision无法完美转发。但这是c ++,所以答案实际上是肯定的。

It requires a touch of boilerplate:

它需要一些样板:

struct A {
  A(int, int){std::cout << "A(int,int)\n"; }
  A(A&&){std::cout<<"A(A&&)\n";}
};

template<class F>
struct maker_t {
  F f;
  template<class T>
  operator T()&&{ return f(); }
};

template<class F>
maker_t<std::decay_t<F>> maker( F&& f ) { return {std::forward<F>(f)}; }

vector<A> v;
v.emplace_back(maker([]{ return A(1,2); }));

live example.

Output is one call to A(int,int). No move occurs. In the making doesn't even require that a move constructor exist (but the vector does, as it thinks it may have to move the elements in an already allocated buffer). In the moves are simply elided.

输出是对A(int,int)的一次调用。没有动作发生。在c ++ 17中,make甚至不需要移动构造函数(但是矢量确实存在,因为它认为它可能必须在已经分配的缓冲区中移动元素)。在c ++ 14中,简单地省略了这些动作。

#4


1  

I just want to add

我只是想补充一下

There is a great 5 minutes lightning talk about copy elision and RVO from Jon Kalb https://youtu.be/fSB57PiXpRw

有关Jon Kalb的复制省略和RVO的5分钟闪电谈话https://youtu.be/fSB57PiXpRw

Also, you might get different results using different compilers gcc, clang or icc

此外,使用不同的编译器gcc,clang或icc可能会得到不同的结果

See compiler explorer, try different compilers and settings and see for yourself

请参阅编译器资源管理器,尝试不同的编译器和设置并亲自查看

https://godbolt.org/g/Yjo9qA

#1


13  

My question is: is the compiler able to optimize this and skip the create and copy? Or should I really try to fix these occurrences?

我的问题是:编译器是否能够对此进行优化并跳过创建和复制?或者我应该真的尝试修复这些事件?

It can't avoid a copy, in the general case. Since emplace_back accepts by forwarding references, it must create temporaries from a pure standardese perspective. Those references must bind to objects, after all.

在一般情况下,它无法避免副本。由于emplace_back通过转发引用接受,因此必须从纯粹的标准角度创建临时值。毕竟,这些引用必须绑定到对象。

Copy elision is a set of rules that allows a copy(or move) constructor to be avoided, and a copy elided, even if the constructor and corresponding destructor have side-effects. It applies in only specific circumstances. And passing arguments by reference is not one of those. So for non-trivial types, where the object copies can't be inlined by the as-if rule, the compiler's hands are bound if it aims to be standard conformant.

复制省略是一组规则,允许避免复制(或移动)构造函数,并且复制省略,即使构造函数和相应的析构函数具有副作用。它仅适用于特定情况。通过引用传递参数不是其中之一。因此,对于非平凡类型,其中对象副本不能通过as-if规则内联,如果编译器的目标是符合标准,则编译器的指针是绑定的。

#2


4  

is the compiler able to optimize this and skip the create and copy?

是编译器能够优化这个并跳过创建和复制?

There is not necessarily a copy involved. If a move constructor is available, there will be a move. This cannot be optimized away, as the direct initialization case will just call the init constructor, while in the other case, the move constructor will be called additionally (including its side-effects).

不一定涉及副本。如果移动构造函数可用,则会有移动。这不能被优化掉,因为直接初始化情况只会调用init构造函数,而在另一种情况下,移动构造函数将被另外调用(包括其副作用)。

Therefore, if possible, you should refactor those codes.

因此,如果可能,您应该重构这些代码。

#3


2  

The easy answer is no; elision doesn't work with perfect forwarding. But this is so the answer is actually yes.

简单的答案是否定的; elision无法完美转发。但这是c ++,所以答案实际上是肯定的。

It requires a touch of boilerplate:

它需要一些样板:

struct A {
  A(int, int){std::cout << "A(int,int)\n"; }
  A(A&&){std::cout<<"A(A&&)\n";}
};

template<class F>
struct maker_t {
  F f;
  template<class T>
  operator T()&&{ return f(); }
};

template<class F>
maker_t<std::decay_t<F>> maker( F&& f ) { return {std::forward<F>(f)}; }

vector<A> v;
v.emplace_back(maker([]{ return A(1,2); }));

live example.

Output is one call to A(int,int). No move occurs. In the making doesn't even require that a move constructor exist (but the vector does, as it thinks it may have to move the elements in an already allocated buffer). In the moves are simply elided.

输出是对A(int,int)的一次调用。没有动作发生。在c ++ 17中,make甚至不需要移动构造函数(但是矢量确实存在,因为它认为它可能必须在已经分配的缓冲区中移动元素)。在c ++ 14中,简单地省略了这些动作。

#4


1  

I just want to add

我只是想补充一下

There is a great 5 minutes lightning talk about copy elision and RVO from Jon Kalb https://youtu.be/fSB57PiXpRw

有关Jon Kalb的复制省略和RVO的5分钟闪电谈话https://youtu.be/fSB57PiXpRw

Also, you might get different results using different compilers gcc, clang or icc

此外,使用不同的编译器gcc,clang或icc可能会得到不同的结果

See compiler explorer, try different compilers and settings and see for yourself

请参阅编译器资源管理器,尝试不同的编译器和设置并亲自查看

https://godbolt.org/g/Yjo9qA