章节回顾:

《Effective C++》第1章 让自己习惯C++-读书笔记

《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记

《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记

《Effective C++》第3章 资源管理(1)-读书笔记

《Effective C++》第3章 资源管理(2)-读书笔记

《Effective C++》第4章 设计与声明(1)-读书笔记

《Effective C++》第4章 设计与声明(2)-读书笔记

《Effective C++》第5章 实现-读书笔记

《Effective C++》第8章 定制new和delete-读书笔记


条款09:绝不在构造和析构过程中调用virtual函数

你不该在构造和析构函数期间调用virtual函数,因为这样的调用不会带来你预期的结果。

(1)在derived class对象的base class构造期间,对象的类型是base class而不是derived class。不只virtual 函数会被编译器解析至base class,若使用运行期类型信息(runtime type information,如dynamic_cast和typeid),也会把对象视为base class类型。

(2)一旦derived class析构函数开始执行,对象内的derived class成员变量便呈现未定义值,所以C++视它们不存在。进入base class析构函数后对象就成为一个base class对象。

一个解决方法是:令derived classes将必要的构造信息向上传递至base classes构造函数。


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

为了实现“连锁赋值”,赋值操作符必须返回一个reference指向操作符的左侧实参。这是你为class实现赋值操作符时应该遵守的协议。

class Widget
{
public:
Widget& operator=(const Widget& ths)
{
...
return *this;
}
};

这个协议不仅适用于以上标准赋值形式,也适用于所有赋值相关运算。

Widget& operator+=(const Widget& rhs)                 // the convention applies to +=, -=, *=, etc. ...
{
...
return *this;
} Widget& operator=(int rhs) //此函数也适用,即使此操作符的参数类型不符协定
{
...
return *this;
}

注意:这只是个协议,并无强制性。如果不遵守它,代码一样可通过编译,这份协议被所有内置类型和标准程序库提供的类型遵守。


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

假设你建立一个class用来保存一个指针指向一块动态分配的位图(bitmap)。

class Bitmap{...};
class Widget
{
...
private:
Bitmap *pb;
}; Widget& Widget::operator=(const Widget &rhs)
{
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}

这是一份不安全的operator=实现,operator=函数内的*this和rhs可能是同一对象,这样delete就不只销毁当前的bitmap,也销毁了rhs的bitmap,导致指针指向一个已删除的对象。下面这种修改方法是行的通的:

Widget& Widget::operator=(const Widget &rhs)
{
if (rhs == *this) return *this; //如果是自我赋值,不做任何事 delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}

这个修改版本虽然具有自我赋值安全性,但不具备异常安全性。如果new Bitmap导致异常,无论是因为分配时内存不足或因为Bitmap的copy构造函数抛出异常,Widget都会持有一个指针指向一块被删除的Bitmap。

实际上,让operator=具备异常安全性往往自动获得赋值安全性。许多时候一群精心安排的语句就可以导致异常安全性。例如以下修改:只需注意在复制pb所指东西之前别删除pb。

Widget& Widget::operator=(const Widget &rhs)
{
Bitmap *pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}

如果new Bitmap抛出异常,pb保持原状,这段代码还可以处理自我赋值或许它的效率不高,但行的通。

如果你关心效率,当然可以把if (rhs == *this) return *this;放在函数起始处。但这时你要考虑一下:自我赋值的频率高吗?因为这项测试需要成本:代码变大了(包括原始码和目标码),并导入了一个新的控制流分支,两者都会降低执行速度。

以上最佳版本还有一个替换版本,就是所谓的copy and swap技术,这个技术和异常安全性有密切关系。

class Widget
{
...
void swap(Widget &rhs);
...
}; Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(ths); //为rhs数据制作一份副本
swap(temp); //交换*this与这份副本
return *this;
}

请记住:

(1)确保当对象自我赋值时operator=有良好行为。技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy and swap。

(2)确定任何函数如果操作一个以上的对象,而其中多个对象是同一对象时,其行为仍然正确。


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

如果你为class新添加一个成员变量,你必须同时修改copying函数,同时也需要修改所有构造函数以及任何非标准形式的operator=。

任何时候只要你承担起“为derived class撰写copying函数”的重大责任,必须很小心地也复制其base class成分。那些成分往往是private,你无法直接访问它们,而应该让derived class的copying函数调用相应的base class函数。

class Customer {};

class PriorityCustomer : public Customer
{
PriorityCustomer(const PriorityCustomer & rhs);
PriorityCustomer& operator=(const PriorityCustomer &rhs);
private:
int priority;
}; PriorityCustomer::PriorityCustomer(const PriorityCustomer & rhs)
: Customer(rhs), priority(rhs.priority) //调用base class的copy构造函数
{ } PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer &rhs)
{
Customer::operator=(rhs); //调用base class的operator=
priority = rhs.priority;
return *this;
}

尽管copying函数有相似地方,但你也不该令copy assignment操作符调用copy构造函数,copy构造函数调用copy assignment操作符同样无意义。

如果你发现你的copy构造函数和copy assignment操作符有相似的代码,消除重复的最好做法是:建立一个新的private成员函数,供二者调用。

请记住:

(1)copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。

(2)不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。

《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记的更多相关文章

  1. 《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记

    章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...

  2. EffectiveC++ 第2章 构造/析构/赋值运算

    我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的"可能比较准确"的「翻译」. Chapter 2 构造 / 析构 / 赋值 条款 05:了解C++ ...

  3. Effective C++ —— 构造/析构/赋值运算(二)

    条款05 : 了解C++默默编写并调用哪些函数 编译器可以暗自为class创建default构造函数.copy构造函数.copy assignment操作符,以及析构函数. 1. default构造函 ...

  4. Effective C++笔记:构造/析构/赋值运算

    条款05:了解C++默默编写并调用哪些函数 默认构造函数.拷贝构造函数.拷贝赋值函数.析构函数构成了一个类的脊梁,只有良好的处理这些函数的定义才能保证类的设计良好性. 当我们没有人为的定义上面的几个函 ...

  5. Effective C++ 笔记二 构造/析构/赋值运算

    条款05:了解C++默默编写并调用哪些函数 编译器默认声明一个default构造函数.一个copy构造函数.一个copy assignment操作符和一个析构函数.这些函数都是public且inlin ...

  6. Effective C++ -- 构造析构赋值运算

    05.了解C++默默编写并调用哪些函数 编译产生的析构函数时non-virtual,除非这个类的基类析构函数为virtual 成员变量中有引用和const成员时,无法自己主动生成copy assign ...

  7. Effective C++笔记(二):构造/析构/赋值运算

    参考:http://www.cnblogs.com/ronny/p/3740926.html 条款05:了解C++默默编写并调用哪些函数 如果自定义一个空类的话,会自动生成默认构造函数.拷贝构造函数. ...

  8. 【Effective C++】构造/析构/赋值运算

    条款05:了解C++默默编写并调用哪些函数 默认构造函数.拷贝构造函数.拷贝赋值函数.析构函数构成了一个类的脊梁,只有良好的处理这些函数的定义才能保证类的设计良好性. 当我们没有人为的定义上面的几个函 ...

  9. Effective C++ 2.构造 析构 赋值运算

    //条款07:为多态基类声明virtual析构函数 // 1.若基类的析构函数不定义为虚函数,由于基类的指针或引用可以指向派生类的对象,则在删除基类对象的时候可能会出错,导致破坏数据结构. // 2. ...

随机推荐

  1. “凯易迅Calix”实习上机——求元音开头辅音结尾的子串

    题目要求: 对于一个输入的字符串(26个小写字母组成),求出他的所有子串中元音开头,辅音结尾的子串.在这所有的子串中,输出第一个和最后一个. 例如aab,共有子串a,aa,aab,ab,b:但是满足元 ...

  2. Table的一些设置(自适应以及溢出)

    table的两个属性 单行溢出点点显示 表格的宽度设置 双栏自适应连续连续英文符换行 1.table重置的两个属性: ①border-collapse: collapse;      /* 为表格设置 ...

  3. CF1153E Serval and Snake(交互题)

    题目 CF1153E Serval and Snake 很有意思的一道交互题 做法 我们观察到,每次查询一行,当这一行仅包含一端是返回的答案是奇数 根据这个性质查询每一行每一列,我们大体能知道两端的位 ...

  4. CSS Margin(外边距)

    CSS Margin(外边距) 一.简介 CSS margin(外边距)属性定义元素周围的空间. margin 清除周围的(外边框)元素区域.margin 没有背景颜色,是完全透明的. margin ...

  5. 20145314郑凯杰 《Java程序设计》第2周学习总结 代码开始!

    ---恢复内容开始--- 20145314郑凯杰 <Java程序设计>第2周学习总结 代码开始! 教材学习内容总结 跟着教材的顺序开始总结我学过的内容: 1编辑.编译.运行教材上代码 这部 ...

  6. mongodb入门很简单(3)

     ##简单的mongodb入门命令## 1.show dbs;      //查看当前数据库 2.use databaseName;         //选库 3.show tables/collec ...

  7. 如何使用curl进行网页授权

    答:使用curl的-u选项,使用方法如下 curl -u username URL (会提示输入密码)

  8. RabbitMQ Network Partitions

    Clustering and Network Partitions RabbitMQ clusters do not tolerate network partitions well. If you ...

  9. java中枚举类的实际应用

    知识点:在Java中,使用枚举类,当遇到实例类型有限的类时,并且数据库中用状态码代表一种含义时,如星期,性别,员工登陆某系统的状态等等, 可以考虑使用枚举类 本例子可以仿照,也可以使用自定义的类型处理 ...

  10. deepin下用命令管理自己的Github仓库

    用命令链接github 初始化 需要用ssh公钥链接到github,注意不能从vim中直接复制 介绍一个命令: xsel < test.txt 将文件中内容直接复制到剪切板中 具体做法 git ...