Luc*_*ore 350 c++ optimization c++-faq return-value-optimization copy-elision
什么是复制省略?什么是(命名)返回值优化?他们意味着什么?
它们会在什么情况下发生?有什么限制?
Luc*_*ore 223
有关技术概述 - 请跳至此答案.
复制省略是大多数编译器实施的优化,以防止在某些情况下额外(可能是昂贵的)副本.它使得按值或按值传递在实践中可行(限制适用).
这是唯一的优化形式,即使复制/移动对象具有副作用,也可以应用(ha!)as-if规则 - 复制省略.
以下来自维基百科的示例:
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();
}
Run Code Online (Sandbox Code Playgroud)
根据编译器和设置,以下输出均有效:
你好,世界!
制作了一份副本.
制作了一份副本.
你好,世界!
制作了一份副本.
你好,世界!
这也意味着可以创建更少的对象,因此您也不能依赖于被调用的特定数量的析构函数.你不应该在copy/move-constructors或析构函数中有关键逻辑,因为你不能依赖它们被调用.
如果省略对副本或移动构造函数的调用,则该构造函数必须仍然存在且必须可访问.这确保了复制省略不允许复制通常不可复制的对象,例如因为它们具有私有或删除的复制/移动构造函数.
C++ 17:从C++ 17开始,当直接返回一个对象时,可以保证Copy Elision:
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
}
Run Code Online (Sandbox Code Playgroud)
Luc*_*ore 91
对于技术较少的观点和介绍 - 跳过这个答案.
复制省略在标准中定义:
如
31)当满足某些条件时,允许实现省略类对象的复制/移动构造,即使该对象的复制/移动构造函数和/或析构函数具有副作用.在这种情况下,实现将省略的复制/移动操作的源和目标视为仅仅两种不同的引用同一对象的方式,并且该对象的销毁发生在两个对象的后期时间.没有优化就被破坏了.123在下列情况下(允许合并以消除多份副本),允许复制/移动操作(称为复制省略)的省略:
- 在具有类返回类型的函数的return语句中,当表达式是具有与函数返回类型相同的cvunqualified类型的非易失性自动对象(函数或catch子句参数除外)的名称时,通过将自动对象直接构造到函数的返回值中,可以省略复制/移动操作
- 在throw-expression中,当操作数是非易失性自动对象的名称(函数或catch子句参数除外),其范围不会超出最内层封闭try-block的末尾(如果有的话)一),通过将自动对象直接构造到异常对象中,可以省略从操作数到异常对象(15.1)的复制/移动操作
- 当一个未绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv-nonqualified类型的类对象时,可以通过将临时对象直接构造到该对象中来省略复制/移动操作.省略的复制/移动的目标
- 当异常处理程序的异常声明(第15条)声明一个相同类型的对象(cv-qualification除外)作为异常对象(15.1)时,可以通过处理异常声明来省略复制/移动操作如果除了为exception-declaration声明的对象执行构造函数和析构函数之外,程序的含义将保持不变,则作为异常对象的别名.
123)因为只有一个对象被破坏而不是两个,并且没有执行一个复制/移动构造函数,所以仍然有一个对象被破坏.
给出的例子是:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
Run Code Online (Sandbox Code Playgroud)
并解释说:
这里可以组合elision的标准来消除对类的复制构造函数的两次调用
Thing
:将本地自动对象复制t
到临时对象中以获取函数的返回值f()
以及将该临时对象复制到对象中t2
.实际上,t
可以将本地对象的构造视为直接初始化全局对象t2
,并且该对象的销毁将在程序退出时发生.向Thing添加移动构造函数具有相同的效果,但它是从临时对象移动构造到t2
省略的.
Luc*_*ore 84
有关技术概述 - 请跳至此答案.
对于技术较少的观点和介绍 - 跳过这个答案.
(命名)返回值优化是复制省略的常见形式.它指的是通过方法的值返回的对象的副本被省略的情况.标准中列出的示例说明了命名的返回值优化,因为该对象已命名.
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
Run Code Online (Sandbox Code Playgroud)
返回临时值时会发生常规返回值优化:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
return Thing();
}
Thing t2 = f();
Run Code Online (Sandbox Code Playgroud)
复制省略发生的其他常见地方是临时值按值传递:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
void foo(Thing t);
foo(Thing());
Run Code Online (Sandbox Code Playgroud)
或者抛出异常并按值捕获时:
struct Thing{
Thing();
Thing(const Thing&);
};
void foo() {
Thing c;
throw c;
}
int main() {
try {
foo();
}
catch(Thing c) {
}
}
Run Code Online (Sandbox Code Playgroud)
大多数商业级编译器支持复制省略和(N)RVO(取决于优化设置).
Aja*_*dav 48
Copy elision是一种编译器优化技术,可以消除不必要的复制/移动对象.
在以下情况下,允许编译器省略复制/移动操作,因此不能调用关联的构造函数:
#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
Run Code Online (Sandbox Code Playgroud)
即使发生了复制省略并且未调用复制/移动构造函数,它也必须存在并且可访问(就好像根本没有进行优化),否则程序就会形成错误.
您应该仅在不会影响软件可观察行为的地方允许此类复制.复制省略是唯一允许具有(即省略)可观察副作用的优化形式.例:
#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
Run Code Online (Sandbox Code Playgroud)
GCC提供-fno-elide-constructors
禁用复制省略的选项.如果您想避免可能的复制省略,请使用-fno-elide-constructors
.
现在,几乎所有编译器都在启用优化时提供复制省略(如果没有设置其他选项则禁用它).
对于每个复制省略,省略了一个构造和一个匹配的复制销毁,从而节省了CPU时间,并且没有创建一个对象,因此节省了堆栈帧上的空间.
归档时间: |
|
查看次数: |
67524 次 |
最近记录: |