C++ 什么是复制省略和返回值优化?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12953127/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What are copy elision and return value optimization?
提问by Luchian Grigore
What is copy elision? What is (named) return value optimization? What do they imply?
什么是复制省略?什么是(命名)返回值优化?它们意味着什么?
In what situations can they occur? What are limitations?
它们会在什么情况下发生?什么是限制?
- If you were referenced to this question, you're probably looking for the introduction.
- For a technical overview, see the standard reference.
- See common caseshere.
- 如果你被提及到这个问题,你可能正在寻找Introduction。
- 有关技术概述,请参阅标准参考。
- 在此处查看常见案例。
采纳答案by Luchian Grigore
Introduction
介绍
For a technical overview - skip to this answer.
有关技术概述 -跳至此答案。
For common cases where copy elision occurs - skip to this answer.
Copy elision is an optimization implemented by most compilers to prevent extra (potentially expensive) copies in certain situations. It makes returning by value or pass-by-value feasible in practice (restrictions apply).
复制省略是大多数编译器实现的优化,用于在某些情况下防止额外的(可能代价高昂的)复制。它使按值返回或按值传递在实践中可行(有限制)。
It's the only form of optimization that elides (ha!) the as-if rule - copy elision can be applied even if copying/moving the object has side-effects.
这是唯一一种省略(哈!) as-if 规则的优化形式 -即使复制/移动对象有副作用,也可以应用复制省略。
The following example taken from Wikipedia:
以下示例取自维基百科:
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C();
}
int main() {
std::cout << "Hello World!\n";
C obj = f();
}
Depending on the compiler & settings, the following outputs are all valid:
根据编译器和设置,以下输出都是有效的:
Hello World!
A copy was made.
A copy was made.
你好,世界!
复印了一份。
复印了一份。
Hello World!
A copy was made.
你好,世界!
复印了一份。
Hello World!
你好,世界!
This also means fewer objects can be created, so you also can't rely on a specific number of destructors being called. You shouldn't have critical logic inside copy/move-constructors or destructors, as you can't rely on them being called.
这也意味着可以创建更少的对象,因此您也不能依赖被调用的特定数量的析构函数。您不应该在复制/移动构造函数或析构函数中使用关键逻辑,因为您不能依赖它们被调用。
If a call to a copy or move constructor is elided, that constructor must still exist and must be accessible. This ensures that copy elision does not allow copying objects which are not normally copyable, e.g. because they have a private or deleted copy/move constructor.
如果省略了对复制或移动构造函数的调用,则该构造函数必须仍然存在并且必须是可访问的。这确保复制省略不允许复制通常不可复制的对象,例如因为它们具有私有或已删除的复制/移动构造函数。
C++17: As of C++17, Copy Elision is guaranteed when an object is returned directly:
C++17:从 C++17 开始,直接返回对象时保证复制省略:
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C(); //Definitely performs copy elision
}
C g() {
C c;
return c; //Maybe performs copy elision
}
int main() {
std::cout << "Hello World!\n";
C obj = f(); //Copy constructor isn't called
}
回答by Luchian Grigore
Standard reference
标准参考
For a less technical view & introduction - skip to this answer.
对于技术性较低的观点和介绍 -跳到这个答案。
For common cases where copy elision occurs - skip to this answer.
Copy elisionis defined in the standard in:
复制省略在标准中定义:
12.8 Copying and moving class objects [class.copy]
12.8 复制和移动类对象 [class.copy]
as
作为
31) When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization.123This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
— in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cvunqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function's return value
— in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object (15.1) can be omitted by constructing the automatic object directly into the exception object
— when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
— when the exception-declaration of an exception handler (Clause 15) declares an object of the same type (except for cv-qualification) as the exception object (15.1), the copy/move operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration.
123) Because only one object is destroyed instead of two, and one copy/move constructor is not executed, there is still one object destroyed for each one constructed.
31) 当满足某些标准时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本应被删除的时间中的较晚时间没有优化就销毁了。123这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以合并以消除多个副本):
— 在具有类返回类型的函数中的 return 语句中,当表达式是与函数返回类型具有相同 cvunqualified 类型的非易失性自动对象(函数或 catch-clause 参数除外)的名称时,通过将自动对象直接构造到函数的返回值中,可以省略复制/移动操作
— 在 throw 表达式中,当操作数是非易失性自动对象(函数或 catch 子句参数除外)的名称时,其作用域不超出最内层封闭 try 块的末尾(如果存在)一)、从操作数到异常对象的复制/移动操作(15.1)可以通过将自动对象直接构造到异常对象中来省略
— 当尚未绑定到引用 (12.2) 的临时类对象将被复制/移动到具有相同 cv-unqualified 类型的类对象时,可以通过将临时对象直接构造到省略复制/移动的目标
— 当异常处理程序的异常声明(第 15 条)声明了与异常对象(15.1)相同类型(除了 cv 限定)的对象时,可以通过处理异常声明来省略复制/移动操作作为异常对象的别名,如果程序的含义将保持不变,除了为异常声明声明的对象执行构造函数和析构函数。
123) 因为只有一个对象被销毁而不是两个,并且一个复制/移动构造函数没有被执行,所以每构造一个对象仍然销毁一个对象。
The example given is:
给出的例子是:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
and explained:
并解释说:
Here the criteria for elision can be combined to eliminate two calls to the copy constructor of class
Thing
: the copying of the local automatic objectt
into the temporary object for the return value of functionf()
and the copying of that temporary object into objectt2
. Effectively, the construction of the local objectt
can be viewed as directly initializing the global objectt2
, and that object's destruction will occur at program exit. Adding a move constructor to Thing has the same effect, but it is the move construction from the temporary object tot2
that is elided.
这里可以结合省略的标准来消除对类的复制构造函数的两次调用
Thing
:将本地自动对象复制t
到函数返回值的临时对象中,f()
以及将该临时对象复制到 object 中t2
。实际上,本地对象的构建t
可以看作是直接初始化全局对象t2
,而该对象的销毁将在程序退出时发生。向 Thing 添加移动构造函数具有相同的效果,但t2
省略了从临时对象到它的移动构造。
回答by Luchian Grigore
Common forms of copy elision
复制省略的常见形式
For a technical overview - skip to this answer.
有关技术概述 -跳至此答案。
For a less technical view & introduction - skip to this answer.
对于技术性较低的观点和介绍 -跳到这个答案。
(Named) Return value optimization is a common form of copy elision. It refers to the situation where an object returned by value from a method has its copy elided. The example set forth in the standard illustrates named return value optimization, since the object is named.
(命名)返回值优化是复制省略的一种常见形式。它指的是从方法中通过值返回的对象的副本被省略的情况。标准中提出的示例说明了命名返回值优化,因为对象已命名。
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
Regular return value optimizationoccurs when a temporary is returned:
返回临时值时会发生常规返回值优化:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
return Thing();
}
Thing t2 = f();
Other common places where copy elision takes place is when a temporary is passed by value:
发生复制省略的其他常见地方是通过值传递临时值时:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
void foo(Thing t);
foo(Thing());
or when an exception is thrown and caught by value:
或者当一个异常被抛出并被值捕获时:
struct Thing{
Thing();
Thing(const Thing&);
};
void foo() {
Thing c;
throw c;
}
int main() {
try {
foo();
}
catch(Thing c) {
}
}
Common limitations of copy elision are:
- multiple return points
- conditional initialization
- 多个返回点
- 条件初始化
Most commercial-grade compilers support copy elision & (N)RVO (depending on optimization settings).
大多数商业级编译器支持复制省略和 (N)RVO(取决于优化设置)。
回答by Ajay yadav
Copy elision is a compiler optimization technique that eliminates unnecessary copying/moving of objects.
复制省略是一种编译器优化技术,可消除不必要的对象复制/移动。
In the following circumstances, a compiler is allowed to omit copy/move operations and hence not to call the associated constructor:
在以下情况下,允许编译器省略复制/移动操作,因此不调用关联的构造函数:
- NRVO (Named Return Value Optimization): If a function returns a class type by value and the return statement's expression is the name of a non-volatile object with automatic storage duration (which isn't a function parameter), then the copy/move that would be performed by a non-optimising compiler can be omitted. If so, the returned value is constructed directly in the storage to which the function's return value would otherwise be moved or copied.
- RVO (Return Value Optimization): If the function returns a nameless temporary object that would be moved or copied into the destination by a naive compiler, the copy or move can be omitted as per 1.
- NRVO(命名返回值优化):如果函数按值返回类类型,并且返回语句的表达式是具有自动存储期的非易失性对象的名称(不是函数参数),则复制/移动可以省略将由非优化编译器执行的操作。如果是,则直接在函数的返回值将被移动或复制到的存储中构造返回值。
- RVO(返回值优化):如果函数返回一个无名的临时对象,该对象将被朴素的编译器移动或复制到目标中,则可以按照 1 省略复制或移动。
#include <iostream>
using namespace std;
class ABC
{
public:
const char *a;
ABC()
{ cout<<"Constructor"<<endl; }
ABC(const char *ptr)
{ cout<<"Constructor"<<endl; }
ABC(ABC &obj)
{ cout<<"copy constructor"<<endl;}
ABC(ABC&& obj)
{ cout<<"Move constructor"<<endl; }
~ABC()
{ cout<<"Destructor"<<endl; }
};
ABC fun123()
{ ABC obj; return obj; }
ABC xyz123()
{ return ABC(); }
int main()
{
ABC abc;
ABC obj1(fun123());//NRVO
ABC obj2(xyz123());//NRVO
ABC xyz = "Stack Overflow";//RVO
return 0;
}
**Output without -fno-elide-constructors**
root@ajay-PC:/home/ajay/c++# ./a.out
Constructor
Constructor
Constructor
Constructor
Destructor
Destructor
Destructor
Destructor
**Output with -fno-elide-constructors**
root@ajay-PC:/home/ajay/c++# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors
root@ajay-PC:/home/ajay/c++# ./a.out
Constructor
Constructor
Move constructor
Destructor
Move constructor
Destructor
Constructor
Move constructor
Destructor
Move constructor
Destructor
Constructor
Move constructor
Destructor
Destructor
Destructor
Destructor
Destructor
Even when copy elision takes place and the copy-/move-constructor is not called, it must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.
即使发生复制省略并且未调用复制/移动构造函数,它也必须存在且可访问(就像根本没有发生优化一样),否则程序格式错误。
You should permit such copy elision only in places where it won't affect the observable behavior of your software. Copy elision is the only form of optimization permitted to have (i.e. elide) observable side-effects. Example:
您应该只在不会影响软件可观察行为的地方允许此类复制省略。复制省略是唯一允许具有(即省略)可观察副作用的优化形式。例子:
#include <iostream>
int n = 0;
class ABC
{ public:
ABC(int) {}
ABC(const ABC& a) { ++n; } // the copy constructor has a visible side effect
}; // it modifies an object with static storage duration
int main()
{
ABC c1(21); // direct-initialization, calls C::C(42)
ABC c2 = ABC(21); // copy-initialization, calls C::C( C(42) )
std::cout << n << std::endl; // prints 0 if the copy was elided, 1 otherwise
return 0;
}
Output without -fno-elide-constructors
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp
root@ajay-PC:/home/ayadav# ./a.out
0
Output with -fno-elide-constructors
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors
root@ajay-PC:/home/ayadav# ./a.out
1
GCC provides the -fno-elide-constructors
option to disable copy elision.
If you want to avoid possible copy elision, use -fno-elide-constructors
.
GCC 提供了-fno-elide-constructors
禁用复制省略的选项。如果您想避免可能的复制省略,请使用-fno-elide-constructors
.
Now almost all compilers provide copy elision when optimisation is enabled (and if no other option is set to disable it).
现在几乎所有编译器在启用优化时都提供复制省略(如果没有其他选项设置为禁用它)。
Conclusion
结论
With each copy elision, one construction and one matching destruction of the copy are omitted, thus saving CPU time, and one object is not created, thus saving space on the stack frame.
每次复制省略,复制的一次构造和一次匹配销毁都被省略,从而节省了 CPU 时间,并且不创建一个对象,从而节省了堆栈帧上的空间。