章节回顾:

《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-读书笔记


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

当C++处理过一个空类后,编译器就会为其声明(编译器版本的):一个拷贝构造函数、一个拷贝赋值运算符和一个析构函数。如果你没有声明任何构造函数,编译器还会声明一个默认构造函数。所有这些函数都被声明为public且inline的。

例如:class Empty{};本质上是:

class Empty {
public:
Empty() { ... } // default constructor
Empty(const Empty& rhs) { ... } // copy constructor
~Empty() { ... } // destructor
Empty& operator=(const Empty& rhs) { ... } // copy assignment operator
};

说明:

(1)只有当这些函数被调用时,才会被编译器创建出来。

(2)默认构造函数和析构函数的作用例如,调用base classes和non-static成员变量的构造函数和析构函数。

(3)编译器产生的析构函数是non-virtual的,除非这个class的base class自身声明有virtual析构函数。

下面举个例子,说明编译器拒绝为class生出operator=。

template<class T>
class NamedObject
{
public:
NamedObject(std::string& name, const T& value); private:
std::string& nameValue; // this is now a reference
const T objectValue; // this is now const
} std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, );
NamedObject<int> s(oldDog, );
p = s;

C++并不允许“让reference改指向不同对象”,所以拒绝编译赋值那一行代码,同样道理更改变const值也是非法的。如果某个base class将拷贝赋值操作符声明为private,编译器也拒绝为其derived class生出一个拷贝赋值操作符。因为编译器为derived class生成的拷贝赋值操作符想象可以处理base class成分,这是不能做到的。


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

所有编译器产生的函数都是public的,所以为了阻止拷贝构造函数和拷贝赋值运算符产生,需要自行声明。下面提供两种方法来阻止copying。

(1)将成员函数声明为private而且故意不去定义,这样可以阻止拷贝。例如:iostream库中的copy构造函数和copy assignment被声明为private。

class HomeForSale {
public:
...
private:
...
HomeForSale(const HomeForSale&); // declarations only
HomeForSale& operator=(const HomeForSale&);
};

说明:当客户企图拷贝对象时,编译器会阻拦他。当成员函数或friend函数拷贝对象时,连接器会阻拦它。

(2)将连接器错误移至编译器是可能的,而且是好事,越早侦测出问题越好。只要将copy构造函数和copy assignment操作符声明为private,且存在于专门为了阻止copying动作而设计的base class内。

class Uncopyable
{
protected: // allow construction
Uncopyable() {} // and destruction of
~Uncopyable() {} // derived objects...
private:
Uncopyable(const Uncopyable&); // ...but prevent copying
Uncopyable& operator=(const Uncopyable&);
};

然后让类继承Uncopyable,这样任何人包括成员函数或friend函数尝试拷贝对象时,编译器便试着生成一个copy构造函数和一个copy assignment操作符,这些函数的编译器生成版本会尝试调用其base class的对应版本,那些调用会被编译器拒绝。

注意:Uncopyable不一定得以public继承它。

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


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

C++明确指出,当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未定义,实际执行时通常发生的是对象的derived成分没被销毁。

说明:

(1)任何class只要带有virtual函数都几乎确定应该也有一个virtual析构函数。

(2)如果class不含析构函数,通常表示它并不意图被用做一个base class。当class不企图被当作base class,令其析构函数为virtual往往是个馊主意。举例说明:

class Point // a 2D point
{
public:
Point(int xCoord, int yCoord);
~Point();
private:
int x, y;
};

如果int占32bit,那么point对象可被放入64bit缓存中。然而当point的析构函数为virtual时:

要实现出virtual函数,对象必须携带某些信息,用于在运行期决定哪一个virtual函数该被调用。这份信息通常由vptr(virtual table pointer)指针指出。vptr指向一个由函数指针构成的数组,称为vtbl(virtual table)。每一个带有virtual函数的class都有一个相应的vtbl。当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr所指的那个vtbl,编译器在其中寻找适当的函数指针。

如果Point class内含virtual函数,对象的体积会增加。两个int再加上vptr指针的大小。对象不能再被放入64bit缓存器,而且C++的Point对象也不再和其他语言(如C)内的相同声明有着一样的结构,因为其他语言的对象没有vptr,因此也就不能把它传递至其他语言写的函数。除非你明确补偿vptr,但那也丧失了可移植性。

注意:标准库string,STL容器等的析构函数均为non-virtual,所以你不能继承它们,否则可能会出现未定义行为。

令class带一个pure virtual析构函数也是很好的。假设你需要个pure class,但手头没有pure virtual函数。由于抽象class总是企图被当作base class,而又由于base class应该有个virtual析构函数。

class AWOV
{
public:
virtual ~AWOV() = ;
};
AWOV::~AWOV()
{ }

你必须为这个pure virtual析构函数提供一份定义:编译器会在AWOV的derived class的析构函数中创建一个对~AWOV()的调用动作,所以如果你不定义,连接器会报错。

请记住:

(1)并非所有的base class的设计目的都是为了多态用途。而带多态用途的base class应该声明一个virtual 析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual 析构函数。

(2)class的设计目的如果不是作为base class使用,或不是为了具备多态性,就不该声明virtual析构函数。


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

C++并不禁止析构函数吐出异常,但它不鼓励你这样做。考虑下面一个例子:

class DBConnection
{
public:
static DBConnection create();
void close();
}; class DBConn
{
public:
~DBConn()
{
db.close();
}
private:
DBConnection db;
};

它允许客户像这样编程,而不会忘记调用close函数,关闭数据库连接。

{
DBConn dbc(DBConnection::create());
...
}

只要能成功地调用close就好了,如果调用导致一个异常,DBConn的析构函数就会传播该异常,即允许它离开析构函数。有两个方法可以避免:

(1)如果close抛出异常就结束程序。通常通过abort完成:

DBConn::~DBConn()
{
try { db.close(); }
catch (...)
{
std::abort();
}
}

如果程序遭遇一个于析构函数间发生的错误后无法继续执行,强迫结束程序是个合理选项。因为它可以阻止异常从析构函数传播出去(那会导致未定义行为),即abort可以抢先制“不明确”行为于死地。

(2)吞下因调用close而发生的异常

DBConn::~DBConn()
{
try { db.close(); }
catch (...)
{
//制作运转记录,记下对close的调用失败
}
}

尽管吞掉异常是个坏主意,有时也比草率结束程序或不明确行为带来的风险好。

这两个办法都无法对导致close抛出异常的情况作出反应。一个较佳的策略是重新设计DBConn接口,提供一个close函数,如果客户没有主动调用close函数,就由析构函数调用。

class DBConn
{
public:
~DBConn()
{
if (!closed)
{
try
{
db.close();
}
catch (...)
{
//制作运转记录,记下对close的调用失败
}
}
}
void close()
{
db.close();
closed = true;
}
private:
DBConnection db;
bool closed;
};

把调用close的责任从DBConn析构函数转移到客户手上同时DBConn析构函数内含一层双保险。如果某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常,那这个异常必须来自析构函数以外的某个函数。因为析构函数吐出异常是危险的,总会带来“过早结束程序”或“发生不明确行为”的风险。

请记住:

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

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

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

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

    章节回顾: <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. url get与post 请求长度限制

    零.总结 文章数据来源于网络,可能存在变动,但是原理是一样的. HTTP 协议 未规定 GET 和POST的长度限制 GET的最大长度显示是因为 浏览器和 web服务器限制了 URI的长度 不同的浏览 ...

  2. python3_Logging模块详解

    python的logging模块提供了通用的日志系统,可以方便第三方模块或应用使用. 简单使用 import logging # logging.config.fileConfig("./l ...

  3. 为什么修改Host不生效

    开发验证的好好的功能,提测后经常有测试反应功能有bug.很多原因都是测试切换host没生效造成的,为什么切换host后刷新页面了也没生效呢? 不生效原因: Keep-Alive 服务器在响应头设置了 ...

  4. 20145322何志威 《Java程序设计》课程总结

    课程总结 每周读书笔记链接汇总 •第一周读书笔记 •第二周读书笔记 •第三周读书笔记 •第四周读书笔记 •第五周读书笔记 •第六周读书笔记 •第七周读书笔记 •第八周读书笔记 •第九周读书笔记 •第十 ...

  5. java语言学习笔记1

    最近也在春节的节假日里,总是会有各种各样的事情出现,没有很完整的时间来学习java以及其他方面的知识. 从昨天开始有了完整的学习体系,我去娄老师推荐的极客学院网站开始学习java语言的语法.在假期之初 ...

  6. 20145230熊佳炜《网络对抗》实验八:WEB基础

    20145230熊佳炜<网络对抗>实验八:WEB基础 实验目标 Web前端HTML:能正常安装.启停Apache.理解HTML,理解表单,理解GET与POST方法,编写一个含有表单的HTM ...

  7. linux字典生成工具crunch

    安装 tar zxvf crunch-3.6.tgz cd crunch-3.6 gcc -Wall -lm -pthread -std=c99 -m64 -D_LARGEFILE_SOURCE -D ...

  8. js 代码执行时间

      <html> <head> </script> <script> var sTime=new Date().getTime(); alert(&qu ...

  9. 【cs231n】反向传播笔记

    前言 首先声明,以下内容绝大部分转自知乎智能单元,他们将官方学习笔记进行了很专业的翻译,在此我会直接copy他们翻译的笔记,有些地方会用红字写自己的笔记,本文只是作为自己的学习笔记.本文内容官网链接: ...

  10. [小问题笔记(三)] SVN树冲突(Tree Conflict),文件不能提交的解决办法

    传说中SVN的树冲突是由不同开发者删除文件,移动文件神马的造成的. 我们遇到的情况是: 开发人员小B移动了项目中几个文件然后提交.开发人员小L更新项目至最新版本. 获取到移动后的文件则显示文件已被修改 ...