1. 复制构造函数
1.1 定义
复制构造函数是一种特殊的构造函数,形参是本类对象的引用,作用是使用一个已存在的对象(由复制构造函数参数指定)去初始化同类的一个新对象。
语法形式
class 类名
{
public:
类名(类名 &对象名); //复制构造函数
};
类名::类名(类名 &对象名) //类体外实现复制构造函数
{
函数体
}
1.2 调用
- 用类的一个对象初始化该类另一个对象
- 函数的形参是类的对象
- 函数的返回值是类的对象
示例
#include <iostream>
#include <iomanip>
using namespace std;
class Test
{
public:
Test();
Test(const Test &);
~Test();
};
Test::Test()
{
cout << setw(20) << "default constructor " << this << endl;
}
Test::Test(const Test &rhs)
{
cout << setw(20) << "copy constructor " << this << endl;
}
Test::~Test()
{
cout << setw(20) << "destructor " << this << endl;
}
Test fun(Test b) //情况2
{
Test c = b; //情况1
return c; //情况3
}
int main()
{
Test a;
fun(a);
return 0;
}
结果(VS2022 _MSC_VER == 1931 Debug x64)
default constructor 0000005947CFF734
copy constructor 0000005947CFF814
copy constructor 0000005947CFF5F4
copy constructor 0000005947CFF854
destructor 0000005947CFF5F4
destructor 0000005947CFF814
destructor 0000005947CFF854
destructor 0000005947CFF734
分析
main函数中创建对象a,调用默认构造函数Test();fun函数的形参为Test类的对象。形实结合时,为形参b分配内存空间并将实参a的值传递给b,调用复制构造函数Test(const Test &);fun函数中用已存在的对象b初始化同类新对象c,调用复制构造函数Test(const Test &);fun函数中返回值为Test类的对象。执行return语句时,创建无名临时对象并将c的值传递给临时对象,调用复制构造函数Test(const Test &);- 对象
c生存期结束,调用析构函数~Test(); - 对象
b生存期结束,调用析构函数~Test(); - 无名临时对象生存期结束,调用析构函数
~Test(); - 对象
a生存期结束,调用析构函数~Test();
虽然fun函数非常简单,只是将对象传入再传出,但是需要反复调用复制构造及析构函数。是否能够省略冗余的复制操作,提高C++代码运行效率?
2. 复制省略
复制省略是自C++11标准起提出的一项编译优化技术,通过省略复制及移动构造函数的调用,实现零复制的值传递语义。
2.1 强制省略复制操作(自C++17起)
以下情况要求编译器省略类对象的复制和移动构造,即使复制/移动构造函数和析构函数具有可观察的副作用。这些对象将直接构造到它们本来要复制/移动到的内存中。复制/移动构造函数不必存在或可访问。
在
return语句中,当操作数是与函数返回类型相同的类类型纯右值(忽略 cv 限定)时。Test fun() { return Test(); } int main() { fun(); return 0; }结果(
VS2022 _MSC_VER == 1931 Debug x64)default constructor 0000007F781DF664 destructor 0000007F781DF664在对象初始化中,当初始化表达式是与目标对象类型相同的类类型纯右值(忽略 cv 限定)时。
int main() { Test t = Test(); return 0; }结果(
VS2022 _MSC_VER == 1931 Debug x64)default constructor 0000004EE62FF6E4 destructor 0000004EE62FF6E4注:以上规则不再是一项优化技术,而是语言标准。VS2022 Debug和Release版本均执行强制省略复制操作,无法禁用。
疑问:将复制构造函数声明为非公有后,VS2022 Debug和Release版本均编译失败,而g++编译器(g++ (Rev9, Built by MSYS2 project) 11.2.0)编译成功。Why?强制省略复制不是允许复制/移动构造函数不存在或不可访问吗?
2.2 非强制省略复制操作(自C++11起)
以下情况允许但不要求编译器省略类对象的复制和移动构造,即使复制/移动构造函数和析构函数具有可观察的副作用。这些对象将直接构造到它们本来要复制/移动到的内存中。复制构造函数必须存在且可访问,否则程序是病态的。
具名返回值优化(named return value optimization, NRVO):在
return语句中,当操作数是具有自动存储期的non-volatile具名对象,不是函数形参或catch子句形参,且具有与函数返回类型相同的类类型(忽略 cv 限定)时。Test fun() { Test a; return a; } int main() { Test t = fun(); return 0; }结果(
VS2022 _MSC_VER == 1931 Debug x64)default constructor 000000B6CD5EF734 copy constructor 000000B6CD5EF874 destructor 000000B6CD5EF734 destructor 000000B6CD5EF874结果(
VS2022 _MSC_VER == 1931 Release x64)default constructor 0000007E0C91FD28 destructor 0000007E0C91FD28注:VS2022 Debug版本默认禁用NRVO优化,Release版本默认执行NRVO优化。
返回值优化(return value optimization, RVO):在
return语句中,当操作数是无名临时对象,且具有与函数返回类型相同的类类型(忽略 cv 限定)时。自C++17起是强制要求的,参考2.1 强制省略复制操作。在对象初始化中,当源对象是无名临时对象,且与目标对象具有相同类型(忽略 cv 限定)时。自C++17起是强制要求的,参考2.1 强制省略复制操作。