条款05:了解C++默默编写并调用哪些函数

默认构造函数、拷贝构造函数、拷贝赋值函数、析构函数构成了一个类的脊梁,只有良好的处理这些函数的定义才能保证类的设计良好性。

当我们没有人为的定义上面的几个函数时,编译器会给我们构造默认的。

当成员变量里有const对象或引用类型时,编译器会不能合成默认的拷贝赋值函数;当一个基类把它的拷贝赋值函数定义为private时,它的派生类也不无生成默认的拷贝赋值函数,因为它无法完成基类成份的赋值。

条款06:若不想使用编译器自动生成的函数,就该明确拒绝

将拷贝构造函数和拷贝赋值操作符声明为private可以阻止类的拷贝和赋值行为,但是这样做的话,类的其他成员或友元依然可以访问,所以最终的解决方案是只声明不定义。

C++11中已经摒弃了上述的做法,而是通过删除函数来设定,只需要在对应的函数列表后加=delete即可。

我们可以为了阻止拷贝而设计一个基类,这个基类非常简单:

class Uncopyable
{
protected:
Uncopyable();
~Uncopyable();
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};

为了阻止我们设计的其他类的拷贝动作,只需要继承上面这个类即可。

条款07:为多态基类声明virtual析构函数

当你需要手动的去delete一个指向基类的指针的时候,需要将该基类的析构函数设置为virtual,这样可以让delete时,动态的删除可能的派生类的成员。

不要无端的给一个没有virtual成员函数的类的析构函数声明为virtual,因为如果要实现virtual函数必须让类携带更多的信息(存储空间)。

标准的string不含任何virtual函数,所以不要把string定义为某个定义类的基类。类似的情况也发生成STL的其他容器如vector,list,set等上。

为一个抽像基类(不能定义对象实体)声明一个纯虚函数,而且要为这个纯虚函数要提供一份定义。

class AWOV{
public:
virtual ~AWOV() = 0; //声明纯虚析构函数
};
AWOV::~AWOV(){} // pure virtual析构函数的定义

并不是多所有基类设计都是为了多态(经base class接口处理derived class对象)用途,比如说在刚开始定义的Uncopyable类。

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

不要让析构函数吐出异常。如果一个被析构函数调用的函数可能抛出获异常,析构函数应该捕捉任何获异常,然后吞下它们(不传播)或结束程序。

如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非析构函数中)执行该操作。

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

假设你有个class继承体系,用来模拟股市交易如买进、卖出的订单等等。这样的交易一定要经过审计,所以每当创建一个交易对象,在审计日志中也需要创建一笔适当记录。

// 交易的基类
class Transaction
{
public:
Transaction();
virtual void logTransaction()const = 0;
//...
};
Transaction::Transaction()
{
// ...
logTransaction(); // 创建一笔交易的最后一步是日志里记录这笔交易
}
// 买入类
class BuyTransaction :public Transaction
{
public:
virtual void logTransaction()const; // 日志记录此型交易
//...
};
// 卖出类
class SellTransaction :public Transaction
{
public:
virtual void logTransaction()const; // 日志记录此型交易
};

现在如果我们有一笔买入的交易,我们定义了一个买入类的对象BuyTransaction b;

这个时候首先BuyTransaction类的构造函数被调用,但是首先Transaction构造函数一定会更早被调用,因为派生类的构造第一步是构造基类部分,当Transaction构造函数执行到调用logTransaction函数时,执行的是基类的版本,而并不是调用派生类里的logTransaction,虽然我们现在正在创建的是BuyTransaction。

Q:为何不能在基类的构造函数中调用virtual函数?

A:在base class构造函数执行时,derived class的成员变量尚未初始化,如果此时真的去访问派生类的virtual函数,那这个函数还没有被初始化,从而可能导致程序异常奔溃。

其实更根本的的原因是:在derived class对象的base class构造期间,对象的类型是base class而不是derived class。

类似的情况也发生在析构函数内,当BuyTransaction的析构函数执行基类的析构函数时,这时BuyTranscation类的成员将会被编译器忽略,base class执行的virtual函数都是基类里的版本。

这里如果我们还是想实现在创建日志的时候能够动态的创建不同类型的日志,我们可能通过向logTransaction中传入不同的参数来创建不同的日志,此时logTransaction将不再是一个viratual 函数。

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

x=y=z=15实际上是采用右赋值的,这个连锁赋值可以解析为:x=(y=(z=15))。这里15先被赋值给z,然后其结果(更新后的z)再被赋值给y,然后其结果(更新后的y)再被赋值给x。

所以如果我们想保证y和z在完成赋值后能够对自己更新,则需要operator=返回一个本身的引用。

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

我们定义了一个Widget的类,这个类中保存一个从heap中分配得到的指针

class Bitmap;
class Widget{
public:
Widget& operator=(const Widget& rhs);
private:
Bitmap* pb;
};
Widget& Widget::operator=(const Widget& rhs)
{
delete pb; // 释放当前类里的指针
pb = new Bitmap(*rhs.pb); // 拷贝一个新指针
return *this;
}

上面的代码看起来合理,实际上一旦出现自我赋值的时候就很不安全,因为它首先释放掉了内存空间,再从本身内存地址拷贝对象的时候,该地址所指向的对象已经不存在了。

我们可以像下面这样写出异常安全的代码

Widget& Widget::operator=(const Widget& rhs)
{
Bitmap* pOrig = pb; // 保存原来的pb
//令pb指向*pb的一个副本,即使new Bitmap异常也没问题
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}

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

当你编写一个拷贝构造函数或拷贝赋值运算符时,请确保有的局部成员变量都被拷贝以及调用所有的基类的适当的拷贝函数。

虽然拷贝赋值操作符与拷贝构造函数做了类似的事情,但是你不能在拷贝赋值操作里调用拷贝构造函数,因为你试图对一个已经定义的对象从重定义并初始化。

如果你发现你的copy构造函数和copy assignment操作符有相近的代码,消除重复代码的做法是,建立一个新的成员函数来给两者调用。

【Effective C++】构造/析构/赋值运算的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. TextReader 和StreamReader

    TextReader 和StreamReader 目录: 为什么要介绍 TextReader? TextReader的常用属性和方法 TextReader 示例 从StreamReader想到多态 简 ...

  2. Python Challenge 第一关

    偶然在网上看到这个,PYTHON CHALLENGE,利用Python语言闯关,觉得挺有意思,就记录一下. 第0关应该算个入口吧,试了好几次才试出来,没什么代码就不写了.计算一个结果出来就行. 第一关 ...

  3. Codeforces Gym101502 I.Move Between Numbers-最短路(Dijkstra优先队列版和数组版)

    I. Move Between Numbers   time limit per test 2.0 s memory limit per test 256 MB input standard inpu ...

  4. null相关

    本文转自:http://www.cnblogs.com/peida/archive/2013/06/14/Guava_Optional.html null代表不确定的对象: Java中,null是一个 ...

  5. 信息收集渠道:文本分享类网站Paste Site

    信息收集渠道:文本分享类网站Paste Site Paste Site是一种专门的文本分享的网站.用户可以将一段文本性质的内容(如代码)上传到网站,然后通过链接分享给其他用户.这一点很类似于现在的优酷 ...

  6. luogu P1057 传球游戏

    题目描述 上体育课的时候,小蛮的老师经常带着同学们一起做游戏.这次,老师带着同学们一起做传球游戏. 游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同 ...

  7. 我的第一个Java程序HelloWorld

    public class HelloWorld{ public static void main(String [] args){ System.out.println("HelloWorl ...

  8. linux的history命令设置

    history的历史记录,同一个用户的各个会话,读取到的内容也是不一样的,原因是它读取的是shell会话缓存里的内容.只有当用户退出当前会话的时候,会话里的缓存内容才会写入~/.bash_histor ...

  9. oralce中相关的概念整理

    [数据库名]  概念:就是一个数据库的标识,作用等同于我们的身份证的作用,假设一台机器上安装了多个数据库,那么每一个数据库都会有一个数据库名称相应,这些数据库名称在数据库被创建的时候,数据库名称也会被 ...

  10. 最新最全的 Android 开源项目合集

    原文链接:https://github.com/opendigg/awesome-github-android-ui 在 Github 上做了一个很新的 Android 开发相关开源项目汇总,涉及到 ...