《Effective C++》读书笔记第二章:构造析构赋值运算

10 篇文章 3 订阅
2 篇文章 0 订阅

Constructor,Destructors,and Assignment Operator

条款5.了解C++默默编写并调用了那些函数

如果声明一个空类,编译器会为这个空类声明拷贝构造函数,拷贝赋值运算符,默认构造函数以及析构函数,并且这些都是public且是inline,还是trivial(平凡的,无价值的)。
对于trivial,我的理解是:
- 比如trivial default constructor只分配内存,而没有进行对象构造。
- 编译器为空类声明的拷贝构造函数,拷贝赋值运算符通常是使用的bitscopy语意(位逐次拷贝)。

如果一个类中含有const成员或者reference,编译器不会为其生成copy assignment操作符,因为这是不合理的:让reference改指向不同的对象或者改变const成员。另外,如果某个base class的copy assignment操作符声明为private,那么编译器也会拒绝为其derived class生成copy assignment操作符。

请记住:
编译器可以暗自为class创建default构造函数,copy构造函数,copy assignment操作符以及析构函数。

条款6.若不想使用编译器自动生成的函数,应该明确拒绝之

  1. 方法一:将函数声明为private且不需要定义,这样就阻止了编译器帮助生成,但是member函数和friend函数仍然可以调用。
  2. 方法二:private继承自一个类,比如Uncopyable,编译器不会为其再生成copy构造函数以及copy assignment运算符。
class Uncopyable {
protected:
    Uncopyable();
    ~Uncopyable();
private:
    Uncopyable(const Uncopyable&);  //阻止copy
    Uncopyable& operator=(const Uncopyable&);
};
class className : private Uncopyable {
    ...
};

请记住:
为了驳回编译器自动提供的功能,可将相应的成员函数声明为private并且不需要实现。或者使用像Uncopyable这样的base class也是一种做法。

条款7:为多态基类声明虚析构函数

将一个派生类对象动态分配给一个基类指针,最后delete该基类指针时,若base class的析构函数时non-virtual,这时结果是未定义的。通常情况是局部销毁,这会形成资源泄露。
最好的办法就是为base class声明一个virtual析构函数。
通常情况下,只要一个class带有虚函数,那么也可以确定它应该有一个虚析构函数。
例子:
标准的string类是不含有任何虚函数的,有时候将string类当作基类声明的派生类就很有可能发生之前说明的那种未定义行为。
解决的办法:在C++11中,已经有关键字 final 来保证类是禁止派生的。
当析构函数是pure virtual时,也就是不能实例化的抽象类。
析构函数的运作顺序:与构造函数的顺序相反。更深刻的说明见构造析构拷贝语意学

用基类接口来处理派生类对象时,就要声明虚析构函数
请记住:
1. polymorphic(带多态性质的)base class应该声明一个虚析构函数。如果class带有任何虚函数,它就用应该拥有一个虚析构函数。
2. Class的设计目的如果不是作为base class使用,或者不具备多态性,就不应该声明为虚析构函数。

此外构造函数不能是虚函数,因为虚函数是从对象的虚表中找到函数入口,而虚表的设定又依靠的是构造函数,因此两者是矛盾的

条款8:别让异常逃离析构函数

C++标准并不进制析构函数吐出异常,但是不鼓励这样做。
因为如果多个异常同时存在时,程序如果不结束就是未定义的行为。例如容器被销毁时。
如果析构函数执行的某个动作,而这个动作失败会导致异常,这该怎么办?
1. 如果抛出异常,就直接调用std::abort();强迫程序结束,也就阻止异常从析构函数逃离。
2. 吞下抛出的异常,记录调用的失败,虽然这样压制了“某些动作失败”的重要信息,但是这样也比析构函数吐出异常这种可能造成不明确行为的风险好。
如何对异常做出反应呢?如果要处理异常,应该是由来自析构函数之外的的某个函数

请记住:
1. 析构函数绝对不要突出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或这结束程序。
2. 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非析构函数)执行该操作。

条款9:绝不在析构函数和构造函数中调用虚函数

在构造函数中调用虚函数会如何呢?直接给出结论:该函数并不会呈现多态性。考虑派生类的对象的声明:

derived D;  //derived的基类base

对象D调用构造函数时,首先基类base的构造函数会被调用,这个构造函数如果调用了一个虚函数,这个虚函数按照常理来说,应该是调用derived的版本,这样就会出现很多问题:这个时候derived的版本必然会调用一些local变量,因为此时derived的构造函数都还没有被调用,那么local变量必然是没有初始化的,所以这是一张通往不明确行为和彻夜调试大会的直达车票。所以C++不会让你走这条路,因此在base的构造期间,virtual函数并不是virtual函数

条款10:令operator=返回一个reference to *this

连锁赋值:

int x, y, z;
x = y = z = 15; 

被解析为:

x = (y = (z = 15));

为了实现连锁赋值,令operator=返回一个reference to *this。
同样的这个规则也适用于+=,-=,*=一系列的操作符。当然,这只是一个协议,并不是强制,但是,最好还是遵守吧。

请记住:
令赋值操作符(assignment)返回一个reference to *this。

条款11:在operator=中处理自我赋值

尤其是类中含有指针或者引用时,operator=中处理自我赋值。
传统的做法就是在operator=最前面实现一个证同测试(identity test),例如:

className& className::operator=(const className& rhs) {
    if(this == rhs) return *this;
    ...
}

另外的一种方法就是”copy and swap”,例如:

class className {
...
void swap(className& rhs);  //交换*this和rhs中的数据
...
}
className& className::operator=(const className& rhs) {
    className temp(rhs);
    swap(temp);
    return *this;
    ...
}

请记住:
1. 确保当对象自我赋值时operator=有良好的行为。其中技术包括证同测试和”copy and swap”。
2. 确认任何函数如果操作一个以上的对象,而其中的多个对象是同一个对象时,其行为是仍然正确的。

条款12:复制对象时请勿忘其每一个成分

参考自侯捷老师翻译的《Effective C++》中文版第三版

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值