理解特殊成员函数的生成(构造,析构,拷贝构造函数等)

特殊成员函数有哪些

在C++的术语中,特殊成员函数是指C++自己生成的函数。在C++98中有四个:默认构造函数,析构函数,拷贝构造函数,拷贝赋值运算符。这些函数只有在需要的时候才会生成,比如某段代码使用他们,但是没有再类中声明。默认构造函数仅在类完全没有构造函数的时候才会生成(防止编译器为某个类生成构造函数,但是你希望的那个构造函数是由参数的)。生成的特殊成员函数都是隐式的public且inline。除非该类是继承自某个具有虚函数的类,否则生成的析构函数是非虚的
在C++11特殊成员函数又增加了两个:移动构造函数,移动赋值运算符。他们的形式是这样的

class widget {
public:
    ...
    widget(widget&& rhs);
    widget& operator=(widget&& rhs);
    ...
};

综上,特殊成员函数有六个,分别是默认构造函数,析构函数,拷贝构造函数,拷贝赋值运算符,移动构造函数,移动赋值运算符

构造函数和析构函数

默认构造函数只有在这个类完全没有声明构造函数的时候,编译器才会生成默认构造函数。析构函数只有一个,如果类没有声明,编译器就会默认生成一个析构函数。需要注意的是虽然我们总是说析构函数最好要是虚函数,但是默认生成的析构函数并不是析构函数。之所以建议我们在定义析构函数的时候做成虚函数,是从继承的角度考虑的。如果并不考虑继承和多态,就不会考虑析构函数需要定义成虚函数,因为完全不需要考虑真是要执行释放资源的是哪个函数。

拷贝构造函数和赋值拷贝运算符

class widget {
public:
    ... 
    widget(const widget&);
    widget& operator=(const widget&);
};

这里要说一下的是两个拷贝操作是独立的:声明一个不会限制编译器声明另一个。如果你声明一个拷贝构造函数,但是没有声明拷贝赋值运算符,如果写的代码用到了拷贝赋值,编译器会帮助你生成拷贝赋值运算符重载。同样如果声明拷贝赋值运算符但是没有拷贝构造,代码用到拷贝构造编译器器会生成它。上述规则在C++98和C++11都成立。
这里之所以要强调一下是独立是的是为了和下面的移动操作进行区分。而且从严格的逻辑意义上来说。这种独立并不是安全的。从逻辑上来讲,拷贝操作实际执行的是“逐一拷贝成员变量”。我们之所以显示的声明一个拷贝构造函数,大概率是因为对象内的成员并不适合使用默认的拷贝方式。如果这个逻辑成立,那么拷贝赋值运算符也是有必要显示声明的。如果没有声明可能存在潜在的风险。反之有成立。但是C++98并没有做限制,所以从安全的角度考虑,在使用的时候,最好两个一起定义。或者一起不定义。这样避免出现错误的拷贝操作。

移动构造函数和移动赋值运算符

移动操作仅在需要的时候生成,如果生成了,就会对非static数据执行逐成员的移动,那意味着移动构造函数根据rhs参数里面对应的成员移动构造出新的部分,移动赋值运算符根据参数里面对应的非static成员移动赋值。移动构造函数也移动构造基类部分(如果有的话),移动赋值运算符也是移动赋值基类部分。
当对一个数据成员或者基类使用移动构造或者移动赋值时,没有任何保证移动一定会真的发生。逐成员移动,实际上,更像是逐成员移动请求,因为对不可移动类型使用移动操作实际上执行的是拷贝操作。逐成员移动的和兴是对对象使用std::move,然后函数决议时会选择执行移动还是拷贝操作。简单的记就是支持移动就移动,不支持移动就拷贝。
与上面的拷贝操作对应的是移动操作的并不是独立的。这种不独立体现在在C++11里体现了。

  1. 声明了某个移动函数,编译器不再生成另一个移动函数。这与上面拷贝操作生成规则不太一样。这条规则背后的原因也是就是上面说的,如果你声明了某个移动函数,就表明这个类型的移动操作不再是“逐一移动成员比变量”的语义了。即你不需要编译器默认生成的移动函数的语义。因此编译器也不会为你生成另一个移动函数。
  2. 进一步,如果一个类显示声明了拷贝操作,编译器就不会生成移动操作。这种限制的解释是如果声明拷贝操作,就暗示着默认逐成员拷贝操作不适合该类,编译器会明白如果默认拷贝不适合该类,移动操作也可能不适用的。
  3. 另一个方向。声明移动操作是的编译器不会是鞥成拷贝操作。

Rule of Three规则

Rule of Three规则,是高速我们如果你声明了拷贝构造函数,拷贝赋值运算符,或者析构函数三者之一,你应该也声明其余两个。它来源于长期的观察,即用户接管拷贝操作的需求几乎都是因为该类会做其他资源的管理。也几乎意味着无论哪种资源管理如果能在一个拷贝操作内完成,也应该在另一个拷贝操作内完。类的析构函数也需要参与资源的管理(释放资源)。通常意义的资源管理指的是内存(如STL容易会动态管理内存)。这也是为什么标准库里的哪些管理内存的类都声明了“the big three”:拷贝构造,拷贝赋值和析构。
Rule of Three带来的后果是只要出现用户定义的析构函数就意味着简单的逐成员拷贝操作不适合于该类。接着,如果一个类声明了析构也意味着拷贝操作可能不应该自定义生成,因为他们做的事情可能是错的。在C++98提出的时候,上述推理没有猪狗重视,C++11中情况仍然入职,但仅仅是因为限制拷贝生成的条件会破坏老代码。
Rule of Three规则被扣的解释依然生效。再加上对声明拷贝操作阻止移动操作隐式生成的观察,使得C++11不会为哪些用户定义的析构函数的类生成移动操作。仅当下面条件成立,编译器才会生成默认的移动操作。

  • 类中没有拷贝操作
  • 类中没有移动操作
  • 类中没有用户定义的析构

你可能感兴趣的:(C++,开发语言,c++)