拷贝构造函数调用情况分析
- 从一个
ComplexClass
对象创建另一个对象:
- 当使用一个已存在的
ComplexClass
对象来初始化另一个新的ComplexClass
对象时,拷贝构造函数会被调用。例如:
ComplexClass obj1;
ComplexClass obj2(obj1);
- 在这种情况下,
obj2
会通过拷贝obj1
的数据成员来进行初始化,包括动态分配的数组内容。拷贝构造函数需要为新对象分配新的内存,并将原对象数组中的内容逐元素复制到新对象的数组中。
- 对象作为函数返回值:
- 当函数返回一个
ComplexClass
对象时,通常会调用拷贝构造函数来创建一个临时对象,将函数内部的局部对象的数据拷贝到这个临时对象中,然后返回该临时对象。例如:
ComplexClass createComplexClass() {
ComplexClass temp;
return temp;
}
- 在
return temp;
这一行,会调用拷贝构造函数创建一个临时对象,将temp
的数据拷贝到这个临时对象中,然后返回该临时对象。
避免不必要的拷贝构造函数调用(使用移动语义)
- 移动构造函数:
- 移动语义通过移动构造函数和移动赋值运算符来实现。移动构造函数允许我们将一个对象的资源(如动态分配的数组)“移动”到另一个对象,而不是进行深拷贝。这样可以避免不必要的内存分配和数据复制,提高性能。
- 移动构造函数的参数是一个右值引用,通常使用
&&
表示。
- 完整代码示例:
#include <iostream>
#include <cstring>
class ComplexClass {
private:
int* data;
size_t size;
public:
// 构造函数
ComplexClass(size_t s = 0) : size(s) {
if (size > 0) {
data = new int[size];
std::memset(data, 0, size * sizeof(int));
} else {
data = nullptr;
}
}
// 拷贝构造函数
ComplexClass(const ComplexClass& other) : size(other.size) {
if (size > 0) {
data = new int[size];
std::memcpy(data, other.data, size * sizeof(int));
} else {
data = nullptr;
}
}
// 移动构造函数
ComplexClass(ComplexClass&& other) noexcept : size(other.size), data(other.data) {
other.size = 0;
other.data = nullptr;
}
// 析构函数
~ComplexClass() {
delete[] data;
}
// 拷贝赋值运算符
ComplexClass& operator=(const ComplexClass& other) {
if (this != &other) {
delete[] data;
size = other.size;
if (size > 0) {
data = new int[size];
std::memcpy(data, other.data, size * sizeof(int));
} else {
data = nullptr;
}
}
return *this;
}
// 移动赋值运算符
ComplexClass& operator=(ComplexClass&& other) noexcept {
if (this != &other) {
delete[] data;
size = other.size;
data = other.data;
other.size = 0;
other.data = nullptr;
}
return *this;
}
};
ComplexClass createComplexClass() {
ComplexClass temp(10);
return temp;
}
int main() {
ComplexClass obj1(5);
ComplexClass obj2(obj1); // 调用拷贝构造函数
ComplexClass obj3 = createComplexClass(); // 调用移动构造函数(如果支持RVO或NRVO,可能不调用任何构造函数)
return 0;
}
- 代码解释:
- 构造函数:分配动态数组内存并初始化。
- 拷贝构造函数:为新对象分配内存,并将原对象数组内容复制过来。
- 移动构造函数:直接接管源对象的动态数组指针,并将源对象的指针设为
nullptr
,大小设为0。这样避免了内存分配和数据复制。
- 析构函数:释放动态分配的数组内存。
- 拷贝赋值运算符:释放原对象内存,分配新内存并复制数据。
- 移动赋值运算符:释放原对象内存,接管源对象的数组指针并将源对象指针设为
nullptr
,大小设为0。
- 在
createComplexClass
函数中返回temp
对象时,由于支持移动语义,如果编译器支持RVO(返回值优化)或NRVO(命名返回值优化),可能不会调用任何构造函数。否则,会调用移动构造函数将temp
的资源移动到返回的临时对象中。