章节回顾:

《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. JVM调优总结(转)

    本文转自:http://my.oschina.net/xishuixixia/blog/132395 常用的调优参数. 1.堆大小 -Xms和-Xmx用于指定堆大小,我们需要将他们俩设置为一样的值,以 ...

  2. IDEA 编译报错: 未结束的字符串文字

    最近在搞新项目,同事用的eclipse开发,而我用的是ide,项目初始是由同事创建的,项目编码是UTF-8,而我开发的ide工具默认是GBK编码,导致在编译的时候报错: 未结束的字符串文字 这个问题就 ...

  3. java并发之Lock以及和synchronized区别

    从Java5之后,在Java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock. 1.Lock 首先要说明的就是Lock,通过查看Lock的源码可知,Lo ...

  4. Poi读取Excle报错 java.util.zip.ZipException: invalid stored block lengths

    一:Poi读取Excle报错  java.util.zip.ZipException: invalid stored block lengths 系统中需要导出excle签收单,excle模板是预设好 ...

  5. 超详细 Spring @RequestMapping 注解使用技巧 (转)

    @RequestMapping 是 Spring Web 应用程序中最常被用到的注解之一.这个注解会将 HTTP 请求映射到 MVC 和 REST 控制器的处理方法上. 在这篇文章中,你将会看到 @R ...

  6. 比较字符串CompareTo的用法及注意

    CompareTo用法 static void Main(string[] args)         {             string str = "1";        ...

  7. Activiti工作流与spring集成

    一.前言 前面Activiti工作流的学习,说明了Activiti的基本应用,在我们开发中可以根据实际的业务参考Activiti的API去更好的理解以及巩固.我们实际的开发中我们基本上都使用sprin ...

  8. 使用tcpdump 对nginx的access.log 非400的ip进行自动抓包

    #抓取nginx access日志不是 400 的行,找出该ip ,并且启动抓包 #该脚本的执行要放在 crond 里面或者 while循环里面 ################## #网卡名称 ne ...

  9. 5.scala中的对象

    排版乱?请移步原文获得更好的阅读体验 1.单例对象 scala中没有静态类或者静态方法,都是通过object实现的,它表示某个类的单例对象.如object People是class People的单例 ...

  10. Harbor 定制页面 和 二次开发指南

    harbor的官方地址:https://github.com/goharbor/harbor 想对Harbor进行二次开发,首先要指定一个harbor的版本,这里我们以Harbor:1.6.2为例: ...