C++ 对象——程序转换语义
1. 程序转换语义定义
程序转换语义(Copy Elision)是指编译器在特定情况下优化对象的复制和移动操作,直接在目标位置构造对象,从而提高性能并减少资源开销。
2. 定义时初始化
定义时初始化是指在对象创建时通过构造函数直接初始化对象,确保对象在使用前处于有效状态。
示例代码
#include <iostream>
using namespace std;
class X {
public:
int m_i;
X(const X& tmpx) {
m_i = tmpx.m_i;
cout << "X类的拷贝构造函数被调用" << endl;
}
X() {
m_i = 0;
cout << "X类的构造函数被调用" << endl;
}
};
int main() {
X x0;
x0.m_i = 15;
X x1 = x0;
X x2(x0);
X x3 = x0; // 分析这一行
return 0;
}
输出
X类的构造函数被调用
X类的拷贝构造函数被调用
X类的拷贝构造函数被调用
X类的拷贝构造函数被调用
编译器视角
对于 X x3 = x0;
:
X x3;
:分配内存,不调用构造函数。x3.X::X(x0);
:调用拷贝构造函数。
3. 参数的初始化
当对象作为函数参数传递时,C++会根据参数类型决定使用拷贝构造函数或移动构造函数。
示例代码
#include <iostream>
using namespace std;
class X {
public:
int m_i;
X(const X& tmpx) {
m_i = tmpx.m_i;
cout << "X类的拷贝构造函数被调用" << endl;
}
X() {
m_i = 0;
cout << "X类的构造函数被调用" << endl;
}
~X() {
cout << "X类的析构函数被调用" << endl;
}
};
void func(X tmpx) {
return;
}
int main() {
X x0;
func(x0);
return 0;
}
输出
X类的构造函数被调用
X类的拷贝构造函数被调用
X类的析构函数被调用
X类的析构函数被调用
编译器视角
在 func(x0);
中,编译器在 func
函数空间内构造 tmpx
对象,并在返回前析构。
4. 返回值初始化
当函数返回对象时,C++会根据返回类型决定使用拷贝构造函数或移动构造函数。现代C++通常优先使用移动语义。
示例代码
#include <iostream>
using namespace std;
class X {
public:
int m_i;
X(const X& tmpx) {
m_i = tmpx.m_i;
cout << "X类的拷贝构造函数被调用" << endl;
}
X() {
m_i = 0;
cout << "X类的构造函数被调用" << endl;
}
~X() {
cout << "X类的析构函数被调用" << endl;
}
};
X func() {
X x0;
return x0; // 可能会触发拷贝消除
}
int main() {
X my = func();
return 0;
}
输出
X类的构造函数被调用
X类的拷贝构造函数被调用
X类的析构函数被调用
X类的析构函数被调用
编译器视角
在 func
中,编译器可能会优化返回,避免拷贝构造函数的调用。
5. 其他注意事项
- 移动语义:实现移动构造函数和移动赋值运算符,以充分利用移动语义,减少不必要的拷贝。
- 资源管理:确保正确实现拷贝和移动语义,避免资源泄漏和双重释放。
- 异常安全:在实现拷贝和移动构造函数时,确保异常安全,防止在异常发生时导致资源泄漏。
总结
- 定义时初始化:确保对象在创建时有效。
- 参数初始化:对象作为参数传递时可能调用拷贝或移动构造函数。
- 返回值初始化:函数返回对象时优先使用移动构造函数,编译器可能会进行拷贝消除。
- 注意移动语义和资源管理:确保类正确实现移动和拷贝构造函数。
- 异常安全:考虑异常影响,确保程序健壮性。