9.Special member

一、C++特殊成员函数简介

  • C++自动为类生成的特殊成员函数包括:
    • 默认构造函数:无参数创建对象。
    • 拷贝构造函数:以另一个现有对象为模板创建新对象。
    • 拷贝赋值操作符:用另一个现有对象替换现有对象的内容。
    • 析构函数:对象离开作用域时被销毁。

1.1 函数调用示例

在以下示例中,每行代码调用的特殊成员函数是:

1
2
3
4
5
6
7
8
9
10
11
MyVector<int> function(MyVector<int> vec0) { // 拷贝构造函数
MyVector<int> vec1; // 默认构造函数
MyVector<int> vec2{3, 4, 5}; // 初始化列表构造函数
MyVector<int> vec3(); // 错误:尝试声明函数
MyVector<int> vec4(vec2); // 拷贝构造函数
MyVector<int> vec5{}; // 默认构造函数
MyVector<int> vec6{vec3 + vec4}; // 拷贝(或移动)构造函数
MyVector<int> vec7 = vec4; // 拷贝构造函数
vec7 = vec2; // 拷贝赋值操作符
return vec7; // (可能的)拷贝构造函数
}

1.2 拷贝构造函数

拷贝构造函数负责创建对象的深拷贝,必要时进行深度复制。

1
2
3
4
5
StringVector::StringVector(const StringVector &other)
: _logicalSize(other._logicalSize), _allocatedSize(other._allocatedSize) {
_elems = new ValueType[_allocatedSize];
std::copy(other.begin(), other.end(), begin());
}

1.3 拷贝赋值操作符

拷贝赋值操作符需释放对象当前资源后,执行拷贝。注意自赋值情况。

1
2
3
4
5
6
7
8
9
10
StringVector& StringVector::operator=(const StringVector &rhs) {
if (this != &rhs) {
delete[] _elems;
_logicalSize = rhs._logicalSize;
_allocatedSize = rhs._allocatedSize;
_elems = new ValueType[_allocatedSize];
std::copy(rhs.begin(), rhs.end(), begin());
}
return *this;
}

1.4 删除操作

通过显式删除特殊成员函数,可以阻止对象被拷贝。

1
2
3
4
5
6
7
class LoggedVector {
public:
LoggedVector(int num, int denom);
~LoggedVector();
LoggedVector(const LoggedVector &rhs) = delete;
LoggedVector &operator=(const LoggedVector &rhs) = delete;
};

1.5 三法则与零法则

  • 三法则:如果你需要自定义析构函数、拷贝构造函数或拷贝赋值操作符中的任何一个,你可能需要定义所有三个,因为这意味着你的类有资源管理需求。
  • 零法则:如果默认操作符满足需求,最好不要自定义这些特殊成员函数。

二、拷贝问题与优化

在C++中,频繁的对象拷贝可能导致性能问题。考虑以下代码片段,它演示了在一个简单场景中对象如何被创建和拷贝:

1
2
3
4
5
6
7
8
9
10
11
StringVector findAllWords(const string &filename) {
StringVector words; // 创建局部StringVector对象
// 使用ifstream从文件名读取数据
return words; // 返回局部对象的拷贝
}

int main() {
StringVector words; // 默认构造函数创建对象
words = findAllWords("words.txt"); // 调用findAllWords函数
// 打印words内容
}

在没有编译器优化的情况下,这段代码会导致多次StringVector对象的创建和拷贝:

  1. main函数中声明的words对象通过默认构造函数创建。
  2. findAllWords函数中的words对象通过默认构造函数创建。
  3. findAllWords返回时,通过拷贝构造函数创建一个临时对象。
  4. 将临时对象赋值给main中的words对象,调用拷贝赋值操作符。

为了减少不必要的对象拷贝,C++提供了几种优化机制:

2.1 拷贝省略与返回值优化(RVO)

编译器可以优化掉一些不必要的拷贝,特别是函数返回时的临时对象。这种优化称为返回值优化(RVO),直接在调用方的空间创建返回对象,避免额外的拷贝。现代C++编译器智能地应用RVO,减少性能损耗。

2.2 移动语义

C++11引入的移动语义允许对象的资源“转移”而非传统意义上的拷贝。通过使用移动构造函数和移动赋值操作符,可以有效地将一个对象的状态或资源转移到另一个对象,从而避免深度拷贝带来的开销。

例如:

1
StringVector words = findAllWords("words.txt");

如果findAllWords返回的对象利用移动语义,那么这里将不会发生深度拷贝,而是资源的转移。

更详细的移动语义后续讲解。


9.Special member
https://ci-tz.github.io/2024/02/12/9-Special-member/
作者
次天钊
发布于
2024年2月12日
许可协议