要了解技术概述,请跳转到此答案。
对于常见的省略复制情况,请跳转到此答案。
复制省略是大多数编译器实现的一种优化,用于在特定情况下防止额外的(可能昂贵的)复制。它使得按值返回或按值传递在实践中成为可能(有限制)。
这是唯一一种省略(哈!)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();
}
你好世界!
已复制。
已复制。
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
}
如需了解技术概述,请跳转至此答案。
如需简单介绍,请跳转至此答案。
(命名的)返回值优化是一种常见的拷贝省略形式。它指的是从方法中按值返回的对象被省略其拷贝的情况。标准中提出的示例说明了命名返回值优化,因为该对象已经命名。
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
当返回一个临时对象时,会发生常规的返回值优化:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
return Thing();
}
Thing t2 = f();
另一个常见的复制省略发生的地方是当一个对象从临时对象构造时:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
void foo(Thing t);
Thing t2 = Thing();
Thing t3 = Thing(Thing()); // two rounds of elision
foo(Thing()); // parameter constructed from temporary
或者当异常被抛出并被捕获时:
struct Thing{
Thing();
Thing(const Thing&);
};
void foo() {
Thing c;
throw c;
}
int main() {
try {
foo();
}
catch(Thing c) {
}
}
大多数商业级编译器都支持复制省略和(N)RVO(取决于优化设置)。C++17 使上述许多复制省略类别成为强制性要求。
如果想了解更为通俗易懂的内容和导言 - 请跳转至此答案。
对于常见情况下发生复制省略的情况 - 请跳转至此答案。
复制省略 在标准中被定义在:
中,即
当满足特定条件时,实现可以省略类对象的复制/移动构造,即使该对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标视为仅是引用同一对象的两种不同方式,并且该对象的销毁发生在没有优化的情况下两个对象将被销毁的时间中较晚的时间点。这种复制/移动操作的省略称为复制省略,可以在以下情况下使用(可以组合以消除多个副本):给出的示例是:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
并解释:
这里的省略标准可以组合起来,以消除对类
Thing
的两次复制构造函数调用: 将自动局部对象t
的复制到函数f()
的返回值的临时对象中, 以及将该临时对象复制到对象t2
中。实际上,局部对象t
的构造可以被视为直接初始化全局对象t2
, 并且该对象的销毁将在程序退出时发生。添加一个移动构造函数到Thing具有相同的效果,但是省略了从临时对象到t2
的移动构造。
Thing
有一个移动构造函数)。 - huangjl复制省略是一种编译器优化技术,用于消除不必要的对象复制/移动操作。
在以下情况下,编译器允许省略复制/移动操作,从而不调用相关构造函数:
#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()); //RVO, not 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
即使发生拷贝省略并且未调用拷贝/移动构造函数,该函数必须存在并且可访问(好像没有进行任何优化一样),否则程序就是不合法的。
只应在不影响软件观察行为的地方允许此类拷贝省略。拷贝省略是唯一允许具有(即省略)可观察副作用的优化形式。例如:
#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提供-fno-elide-constructors
选项以禁用复制省略。如果您想避免可能的复制省略,请使用-fno-elide-constructors
。
现在几乎所有编译器都会在启用优化时提供复制省略(如果没有其他选项来禁用它)。
每次复制省略都会省略一个构造函数和一个匹配的拷贝对象的析构函数,从而节省CPU时间,并且不会创建一个对象,从而在栈帧上节省空间。
ABC obj2(xyz123());
是NRVO还是RVO?这个语句不会获得和ABC xyz = "Stack Overflow"; // RVO
一样的临时变量/对象吗? - Asif Mushtaq这里我提供另一个复制省略的例子,显然今天我遇到了。
# include <iostream>
class Obj {
public:
int var1;
Obj(){
std::cout<<"In Obj()"<<"\n";
var1 =2;
};
Obj(const Obj & org){
std::cout<<"In Obj(const Obj & org)"<<"\n";
var1=org.var1+1;
};
};
int main(){
{
/*const*/ Obj Obj_instance1; //const doesn't change anything
Obj Obj_instance2;
std::cout<<"assignment:"<<"\n";
Obj_instance2=Obj(Obj(Obj(Obj(Obj_instance1)))) ;
// in fact expected: 6, but got 3, because of 'copy elision'
std::cout<<"Obj_instance2.var1:"<<Obj_instance2.var1<<"\n";
}
}
结果如下:
In Obj()
In Obj()
assignment:
In Obj(const Obj & org)
Obj_instance2.var1:3