Chapter 2
作者:互联网
2 构造/析构/赋值运算
条款 05 了解 C++ 默认编写并调用哪些函数
-
当没有在类中显示声明,编译器则会声明默认版本的 copy 构造函数、 copy assignment 操作符和一个析构函数。
-
编译器默认生成的函数是 public 和 inline 的。
class Empty{ }; Empty e1;//default constructor Empty e2(e1);//copy constructor e2 = e1;//copy assignment operator //初始化都是调用 构造函数 而不是 copy assignment Empty e3 = e1;//copy construct
-
default contructor
调用base class
和non-static
成员变量的构造函数和析构函数。 -
若
base class
的析构函数没有声明为virtual
,则编译器默认生成的版本是个non-virtual
。 -
copy constructor
和copy assignment operator
行为基本一致,都会调用成员变量的对应copy constructor
和copy assignment operator
(若有),对于内置类型则拷贝其中每一个比特来完成初始化。 -
有几种情况,编译器拒绝生成默认的
copy assignment operator
:- 类的成员变量含有引用类型。
- 类的成员变量含有
const
类型。 - 某个
base class
的copy assignment operator
声明在private
中,则作为派生类无法生成默认的函数,因为派生类会处理base class
部分。
*请记住 : *
1.编译器可以暗自为 class 创建 default 构造函数、copy构造函数、copy assignment操作符、析构函数。
条款 06 若不想使用编译器自动生成的函数,就该明确拒绝
-
若手动声明实现相应的函数,则编译器不会自动生成默认的版本,但有时候需要没有参数或者有缺省参数的默认版本。
-
若明确不希望所创造的 class 能够赋值,则需要拒绝自动生成的
copy constructor
。有几种拒绝方式 :
-
C++11
: 在声明没有参数或有缺省参数的构造函数版本后加上=default
,手动生成默认版本。 在需要拒绝生成的函数声明后加上
=delete
。例:
class Empty { public: Empty(size = 0) = default;//手动声明默认版本 Empty(const Empty &) = delete;//拒绝编译器自动生成的 copy constructor Empty& operator=(const Empty &) = delete; private: int size; };
-
将
copy constructor
和copy assignment operator
声明在private
中。此时友元函数和公有成员仍可访问所以不太安全,但如果不去定义它们,连接器仍然会报错。 -
声明一个阻止赋值的
base class
class Uncopyable{ protected: Uncopyable() {} ~Uncopyable() {} private: Uncopyable(const Uncopyable &); Uncopyable& operator=(const Uncopyable &); }; class HomeForSale : private Uncopyable{ };
请记住 :
1. 为驳回编译器自动提供的功能,可将相应的成员函数声明为private
并且不予实现。或使用无法复制的base class
。同样可以使用C++11
的做法。
条款 07 为多态基类声明 virtual 析构函数
根据C++的多态特性,基类指针可以指向派生类。例 :
class TimeKeeper{
public:
TimeKeeper();
~TimeKeeper();
};
class AtomicClock : public TimeKeeper {...};
class WaterClock : public TimeKeeper {...};
class WristWatch : public TImeKeeper {...};
//getTimeKeeper()返回派生类指针
TimeKeeper *ptk = getTimeKeeper();//ptk指向派生类,这就是多态特性
delete ptk;
但问题是ptk
是一个基类指针,所以当 delete ptk 时,会调用基类的析构函数,而基类的析构函数时non-virtual
的,因此 ptk 所指向的派生类中有一部分不从基类继承过来的成员没有被释放,这回造成资源泄露。
解决方法:
为基类析构函数加上virtual
class TimeKeeper{
public:
TimeKeeper();
virtual ~TimeKeeper();
};
此时,系统会根据 ptk 实际指向的派生类调用相应的重写的析构函数。
注 : 这是C++通过虚函数表(virtual table)来实现的。
1. 维护一个 vptr
指针。
2. vptr
指向数组vtbl
(virtual table)。
3. 每个带有virtual
函数的类都有一个相应的虚函数表vtbl
,当对象调用虚函数是,实际 调用的函数取决于vptr
所指向的虚函数表,编译器会在其中寻找相应的函数指针。
当你声明抽象基类而又没有相应的纯虚函数时,可以将析构函数声明为纯虚函数,但必须要为它提供定义。
*请记住 : *
1. polymorphic(带多态性质的) base classes 应该声明一个 virtual
析构函数。如果 class 带有任何 virtual
函数(证明要充当某些类的基类),他就应该拥有一个 virtual
析构函数。
2. Classes 的设计目的如果不是作为 base classes 使用,或不是为了其多态特性,就不应该声明virtual
析构函数。
条款 08 别让异常逃离析构函数
当一个对象销毁时会自动调用析构函数,它负责释放此对象所占用的资源,若在析构函数中出现异常,且在析构函数内没有捕捉,那么该异常会向上传递,或许你在上层捕捉它并做出相应处理或许程序退出,但不要忘了此时你的析构函数还没有执行完,也就是说你的资源没有释放。
**解决方法 : **在析构函数中捕捉异常,确保析构函数能够顺利执行完。
*请记住 : *
1. 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后痛下它们或结束程序。
条款09 决不在构造和析构过程中调用 virtual 函数
构造函数 : 当在基类构造函数中包含virtual
函数,假设该类有派生类,由于在派生类构造函数调用时,会 先调用基类构造函数,此时派生类成员未初始化,那么基类中的virtual
函数不会显示多态性, 不会调用相应的virtual
函数。
析构函数 : 同理,当派生类的的析构函数运行时,派生类的成员可能会被释放,所以C++干脆把他们视为不存 在,进入base class
析构函数后,对象就变成一个基类对象,此时virtual
没有意义。
根本原因 : 派生类的基类构造函数构造析构期间,派生类对象被视为base class
而不derive dclass
。
*请记住 : *
1. 在构造和析构期间不要调用virtual
函数,因为这类调用从不下降至derived class
(相对当前执行构造函数和析构函数的那一层)。
条款10 令 operator= 返回一个 reference to *this
这样的以进行连续赋值操作,就像内置类型 :
int z, y, z;
x = y = z = 1;
此条同样适用其他像 +=、-=等与赋值相关的运算符。
请记住 :
1. 令赋值操作符返回一个reference to *this
。
条款11 在 operator= 中处理 “自我复制”
在定义 operator=
函数时,注意要先判同,防止自我赋值,自我复制有时会导致自己的数据被释放掉导致出错。
也可以适用 copy and swap
技术,起始就是先将参数复制一份副本,再适用标准库的swap()
函数。
请记住 :
1. 确保当对象自我赋值时 operator= 有良好的行为。其中技术包括比较"来源对象"和"目标对象"的地址
、精心周到的语句顺序、以及 copy and swap。
2. 确定任何函数如果操作一个以上的对象,而其中多个对象是同一对象时,其行为仍然正确。
条款12 复制对象时勿忘其每一个成分
当编写一个copying
函数时。请确保 :
1. 复制所有 local 成员。
2. 调用所有base class
内的适当的 copying
函数。
注意,复制构造函数和复制操作符往往有有着相同的操作,但不应该为了去除冗余代码而相互调用。
理由:
1. 用 copy assignment
操作符调用 copy
构造函数 : 试图构造一个已经存在的对象,逻辑上不太合理,可 能代码能实现相应的功能,但不推荐使用。
2. 用 copy
构造函数调用copy assignment
操作符 : 同上分析。
若想去除冗余代码,可以将冗余代码放进第三个函数中,供两者调用。
请记住 :
1. Copying 函数应该确保赋值“对象内的所有成员变量”及“所有base class
成分。”
2. 不要尝试以某 copying函数实现另一个 copying 函数。应该将共同技能放进第三个函数中,共同调用。
标签:Chapter,函数,virtual,析构,copy,class,构造函数 来源: https://www.cnblogs.com/Lingh/p/16618435.html