版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_18218335/article/details/84680685
构造、析构、赋值运算
了解C++ 默认编写并调用哪些函数
如果没有声明,编译器将为C++ 类声明(编译器版本的)构造函数、一个拷贝构造函数、拷贝赋值操作符和一个析构函数,它们都是public、inline
-
默认行为
-
拷贝构造函数、拷贝赋值操作符
-
单纯的将来源对象的每个非静态成员变量拷贝到目标对象
-
内置数据类型
- 字节拷贝
-
非内置数据类型
- 存在拷贝构造函数则调用,否则就执行同样的,编译器生成的拷贝构造函数
-
一般而言,只有当生出的代码合法且有适当机会证明它有意义,编译器才会为class 生出operator=
- 如果打算在一个”内含reference 成员或const 成员“的class 内支持赋值操作,必须定义自己的拷贝赋值操作符
-
-
构造函数、析构函数
-
给编译器一个地方用来放置“藏身其后“的代码
-
默认构造函数和析构函数
- 调用基类和非静态成员变量的构造函数和析构函数
- 编译器产出的析构函数是非虚函数,除非这个基类自身声明有虚析构函数
-
-
唯有这些函数被需要(被调用),它们才会被编译器创建出来
若不想使用编译器自动生成的函数,就应该明确拒绝
所有编译器产出的函数都是public,为了阻止这些函数被创建,需要自行声明它们
将不需要的函数,声明为private
- 成员函数和友元函数,也会也可以调用这个私有函数
- 直接不定义该函数,当引用的时候,会产生,找不到符号的链接错误
- ios_base、basic_ios 它们的拷贝构造函数和拷贝赋值函数都被声明为私有,没有定义
这里提供了一个阻止copying 动作的基类,其拷贝构造函数和拷贝赋值函数为私有,且未实现,当子类继承,尝试进行拷贝构造操作,生成默认的拷贝构造函数,其访问父类的拷贝构造函数时,发现为私有,因此失败
为驳回编译器自动提供的机能,可将相应的成员函数声明为私有,且不予实现。使用像Uncopyable 的基类也是一种做法
为多态基类声明virtual 析构函数
核心:以析构子类特有的资源,而这是必然的需求
- 否则,当子类没有定义其析构函数,或者,定义了,但,通过delete 父类指针,的方式,析构,不会调用,非虚的子类函数,导致,子类未析构完全
如果类不含虚函数,通常表示它并不意图被用做一个基类。当类不企图被当作基类,其析构函数为虚函数则是不对的
- 一个是占用更大的空间(虚表指针的原因)
- 其对象不能再和其它语言(如C)内的相同声明有同样的结构(虚表指针的原因,当然,可以通过定制C 接口实现交互,但不直观)
当类中至少含有一个虚函数,才为它声明虚析构函数
细节
-
当希望类成为抽象类,但没有纯虚函数,可以将析构函数构建为纯虚析构函数
- 但同时,需要为该析构函数提供一份定义,否则,其派生类的析构函数无法引用它
-
STL 或标准库或已经存在的,不包含虚函数的类,我们不应该继承它们
- 比如STL 容器、std::string
总结
- 带多态性质的(polymorphic)基类应该声明一个虚析构函数
- 类的设计目的不是作为基类使用,或不是为了具备多态,就不应该声明虚析构函数
别让异常逃离析构函数
简单场景
- 包含10 个对象指针的数组,析构到中间,产生异常,如果抛出,剩下的不析构,程序状态不稳定
- 如果,已经产生了异常, 而栈展开时调用某个对象的析构函数,此时抛出异常, 程序状态不稳定
析构函数中产生错误怎么办
-
产生异常就记录并退出进程
-
记录该异常,程序继续执行
- 即使记录了问题的产生,程序继续执行可能破坏更多的数据
-
比较好的一种办法是,自己在析构函数中,可以选择,记录并退出,或者记录并继续执行。但同时提供一个public 的close 函数,让客户调用,给客户一个,捕获该异常,并选择自己需要的处理的方式
原则
- 析构不要抛出异常,如果可能产生异常,应该捕捉,记录,然后继续执行,或结束进程
- 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class 应该提供一个普通函数执行该操作
绝不在构造函数和析构函数中调用虚函数
基类构造期间,虚函数不会下降到继承类的阶层, 对象的行为此时就像基类一样:基类构造期间,虚函数不是虚函数,是自己的
- 此时即使,RTI 都认为它是基类
不仅要确保其自身没有调用虚函数,其所调用的所有下层函数都不能调用虚函数(下层函数调用虚函数时,编译器甚至连警告都不给了)
如何,保证,子类构建函数一定要提供某个特有属性
- 书中的例子,要求子类给父类构造函数传递自己的,string 参数,以,输出特定的信息
- 令继承类将必要的构造信息向上传递到基类构造函数,以此弥补这种多态的不足
构造函数和析构期间,不要调用虚函数,这类调用从不下降到派生类(比起当前执行构造函数和析构函数的那层)
令operator= 返回一个reference to *this
是一个简单的,协议,这份协议被所有内置类型、标准程序库提供的类型:string、vector、complex、等遵守
在operator= 中处理”自我赋值“
if(this == & rhs) return *this;
-
需要判断操作
-
影响程序执行逻辑
- 预加载,缓存,pipelining?
T* pOrig = pb;
pb = new T(rhs.pb);
delete pOrig;
return *this;
- 这种做法,不用判断,但对某些对象来说,可能,这里的T 的构造比判断的开销更大
- 不够高效
swap 函数
-
copy and swap
-
就是,拷贝构造函数,然后,交换数据
- 参数引用,内部显式的构造函数
- 参数直接值传递,内部,省去构造函数调用,直接swap
原则
- 确保当对象自我赋值时operator= 有良好的行为。技术包括:比较来源对象和目标对象的地址、精心设计的语句顺序、copy-and-swap
- 确定任何函数如果操作到一个以上的对象,其中多个对象是同一个对象时,其行为仍然正确
复制对象时,勿忘其每一个成分
当提供自己的拷贝构造函数、拷贝赋值函数时尤其注意
新添加成员变量时,计得向函数中添加它
继承关系中的复制
-
拷贝构造函数
- 初始值表中添加:父类(参数),比如:
Son::Son(para1,para2):Far(para1),m_m1(para2){}
- 拷贝赋值函数
Son& Son::operator=(const Son& rhs){
Far::operator=(rhs);}
-
拷贝构造函数和拷贝赋值函数不可互相调用,概念不同
- 可以提供一个共有的成员函数,提供共有的拷贝操作
原则
- Copying 函数应该确保复制”对象内的所有成员变量“及“所有基类成分”
- 不要尝试以某个copying 函数实现另一个copying 函数。应该将共同机能放进第三个函数中,并由两个copying 函数共同调用