C++类中的特殊成员函数
C++类中存在一些特殊的成员函数,编译器通常会自动为我们生成这些函数。然而,在某些情况下,编译器可能不会自动创建这些函数,因此我们需要了解这些函数的生成规则,并做好记录。
- 默认构造函数。仅当类不存在用户声明的构造函数时才自动生成。也就是啥构造函数都没定义,编译器才会自动生成。
- 析构函数。编译器总是会被自动生成,除非你自定义了它。
- 拷贝构造函数。
- 拷贝构造和拷贝赋值之间是独立的互不干扰,自定义了拷贝赋值运算符,编译器还是会帮我们生成拷贝构造函数(理论上定义了一个拷贝操作,另外一个也应该定义的,目前好像不定义编译还是会生成,但是会生成警告)。定义了一个拷贝操作,另外一个也应该定义的,目前好像不定义编译还是会生成,但是会生成警告)。定义的,目前好像不定义编译还是会生成,但是会生成警告)。不定义编译还是会生成,但是会生成警告)。
- 拷贝和移动之间是互斥的,自定义了移动操作(移动构造函数或移动赋值运算符),编译器就不会为你生成拷贝构造函数和拷贝赋值运算符(编译器通过给拷贝操作加上delete来保证)。
- 析构函数不会影响拷贝操作,自定义了析构函数,编译器还是会为我们生成拷贝构造函数。(理论上按照big_three理论,自定义了析构函数,拷贝操作应该也自定义了,毕竟需要自己进行资源释放,那资源的拷贝也应该需要特殊处理,好像编译器会生成警告)
- 拷贝赋值运算符。
- 拷贝构造和拷贝赋值之间是独立的互不干扰,自定义了拷贝构造函数,编译器还是会帮我们生成拷贝赋值运算符(理论上定义了一个拷贝操作,另外一个也应该定义的,目前好像不定义编译还是会生成,但是会生成警告)。定义了一个拷贝操作,另外一个也应该定义的,目前好像不定义编译还是会生成,但是会生成警告)。定义的,目前好像不定义编译还是会生成,但是会生成警告)。
- 拷贝和移动之间是互斥的,自定义了移动操作(移动构造函数或移动赋值运算符),编译器就不会为我们生成拷贝构造函数和拷贝赋值运算符(编译器通过给拷贝操作加上delete来保证)。
- 析构函数不会影响拷贝操作,自定义了析构函数,编译器还是会为我们生成拷贝赋值运算符。
- 移动构造函数。
- 移动操作之间不是独立的,自定义了移动赋值运算符,编译器就不会为我们自动生成移动构造函数。理由:如果你给类声明了,比如,一个移动构造函数,就表明对于移动操作应怎样实现,与编译器应生成的默认逐成员移动有些区别。如果逐成员移动构造有些问题,那么逐成员移动赋值同样也可能有问题。所以声明移动构造函数阻止移动赋值运算符的生成,声明移动赋值运算符同样阻止编译器生成移动构造函数。
- 移动和拷贝动作互斥,自定义了任何拷贝操作(拷贝构造活拷贝赋值运算符),编译器就不会为我们生成移动构造或者移动赋值。理由:如果声明拷贝操作(构造或者赋值)就暗示着平常拷贝对象的方法(逐成员拷贝)不适用于该类,编译器会明白如果逐成员拷贝对拷贝操作来说不合适,逐成员移动也可能对移动操作来说不合适。
- 析构函数会影响移动操作,自定义了析构函数,编译器还是会为我们生成移动构造函数。
- 移动赋值运算符。
- 移动操作之间不是独立的,自定义了移动构造,编译器就不会为我们自动生成移动赋值运算符。理由同上
- 移动和拷贝动作互斥,自定义了任何拷贝操作(拷贝构造活拷贝赋值运算符),编译器就不会为我们生成移动构造或者移动赋值。理由同上
- 析构函数会影响移动操作,自定义了析构函数,编译器还是会为我们生成移动构造函数。
注意没有“成员函数模版阻止编译器生成特殊成员函数”的规则。这意味着如果Widget是这样:1
2
3
4
5
6
7
8
9class Widget {
…
template<typename T> //从任何东西构造Widget
Widget(const T& rhs);
template<typename T> //从任何东西赋值给Widget
Widget& operator=(const T& rhs);
…
};
编译器仍会生成移动和拷贝操作(假设正常生成它们的条件满足),即使可以模板实例化产出拷贝构造和拷贝赋值运算符的函数签名
除此之外,现代C++还有一个三/五法则,也是对这方面的总结,感兴趣可以去了解一下。
参考:
https://learn.microsoft.com/zh-cn/cpp/cpp/explicitly-defaulted-and-deleted-functions?view=msvc-170