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

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

编译器默认声明一个default构造函数、一个copy构造函数、一个copy assignment操作符和一个析构函数。这些函数都是public且inline。

1 class Empty {
2 public:
3     Empty() {...}
4     Empty(const Empty& rhs) {...}
5     ~Empty() {...}
6     Empty& operator=(const Empty& rhs) {...}
7 };

如果你打算在一个内含reference成员的class内支持赋值操作,你必须自己定义copy assignment操作符。如果某个base classes将copy assignment操作符声明为private,编译器将拒绝为其derived classes生成一个copy assignment操作符。

记住:

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

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

如果不声明copy构造函数或copy assignment操作符,编译器可能为你产出一份,于是你的classes支持copying。所有的编译器产出的函数都是public。为了阻止这些函数被创建出来,将他们声明为private。但是member函数和friend函数还是可以调用你的private函数。如果不去定义它们,那么在某些人不慎调用时会得到一个连接错误。

将连接期错误移至编译期是可能的。设计一个阻止copying操作的base class。

1 class Uncopyable {
2 protected:
3     Uncopyable() {}
4     ~Uncopyable() {}
5 private:
6     Uncopyable(const Uncopyable&);
7     Uncopyable& operator=(const Uncopyable&);
8 };

记住:

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

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

当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义。

给base class一个virtual析构函数。此后删除derived class对象就会如你想要的那般。

任何class只要带有virtual函数都几乎确定应该也有一个virtual析构函数。当class不企图被当做base class,令其析构函数为virtual往往是个馊主意。

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

为你希望它称为抽象的那个class声明一个pure virtual析构函数。

1 class AWOV {
2 public:
3     virtual ~AWOV()=0;
4 };
5 AWOV::~AWOV(){}

析构函数运作的方式是,最深层派生的那个class其析构函数最先被调用,然后是其每一个base class的析构函数被调用。给base classes一个virtual析构函数,这个规则只适用于带多态性质的base classes身上。这种base classes的设计目的是为了用来通过base class接口处理derived class对象。而并非所有base classes的设计目的都是为了多态用途。

记住:

带多态性质的base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,他就应该拥有一个virtual析构函数。

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

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

C++并不禁止析构函数凸出异常,但它不鼓励你这样做。在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为。

如果程序遭遇一个于析构期发生的错误后无法继续执行,强迫结束程序是个合理选项。将异常吞掉是个坏主意,它压制了某些动作失败的重要信息。

一个较佳策略是重新设计接口,使其客户有机会对可能出现的问题作出反应。

记住:

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

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

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

base class构造期间virtual函数绝对不会下降到derived classes阶层。在derived class对象的base class构造期间,对象的类型是base class而不是derived class。不只virtual函数会被编译器解析至base class,若使用运行期类型信息,也会把对象视为base class类型。

相同道理也适用于析构函数。一旦derived class析构函数开始执行,对象内的derived class成员变量便呈现未定义值,所以C++视它们仿佛不再存在。进入base class析构函数后对象就成为一个base class对象。

由于无法使用virtual函数从base classes向下调用,在构造期间,你可以藉由令derived classes将必要的构造信息向上传递至base class构造函数替换之而加以弥补。

记住:

在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class。

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

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

1 class Widget {
2 public:
3     Widget& operator=(const Widget& rhs) {
4         ...
5         return *this;
6     }
7 };

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

记住:

另赋值操作符返回一个reference to *this。

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

自我赋值发生在对象被赋值给自己时。

欲阻止这种错误,传统做法是藉由operator=最前面的一个证同测试达到自我赋值的检验目的。

1     Widget& operator=(const Widget& rhs) {
2         if (this==&rhs) return *this;
3         ...
4         return *this;
5     }

这个版本仍然不具备异常安全性,而让operator=具备异常安全性往往自动获得自赋值安全的回报。

另一个替代方案是,使用copy and swap技术。

1 class Widget {
2 public:
3     Widget& operator=(const Widget& rhs) {
4         Widget temp(rhs);
5         swap(temp);
6         return *this;
7     }
8 };

记住:

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

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

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

既然你拒绝编译器为你写出copying函数,如果你的代码不完全,它们也不告诉你。如果你为class添加一个成员变量,你必须同时修改copying函数。

任何时候只要你承担起为derived class撰写copying函数的重责大任,必须很小心的也复制其base class成分。你应该让derived class的copying函数调用相应的base class函数。

当你编写一个copying函数,请确保:复制所有local成员变量;调用所有base classes内适当的copying函数。

如果你发现你的copy构造函数和copy assignment操作符有相近的代码,消除重复代码的做法是,建立一个新的成员给两者调用。这样的函数往往是private而且常被命名为init。这个策略可以安全消除copy构造函数和copy assignment操作符之间的代码重复。

记住:

Copying函数应该确保复制对象内的所有成员变量及所有base class成分。

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

时间: 2024-10-21 06:53:46

Effective C++ 笔记二 构造/析构/赋值运算的相关文章

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

条款05:了解C++默默编写并调用哪些函数 默认构造函数.拷贝构造函数.拷贝赋值函数.析构函数构成了一个类的脊梁,只有良好的处理这些函数的定义才能保证类的设计良好性. 当我们没有人为的定义上面的几个函数时,编译器会给我们构造默认的. 当成员变量里有const对象或引用类型时,编译器会不能合成默认的拷贝赋值函数:当一个基类把它的拷贝赋值函数定义为private时,它的派生类也不无生成默认的拷贝赋值函数,因为它无法完成基类成份的赋值. 条款06:若不想使用编译器自动生成的函数,就该明确拒绝 将拷贝构

【Effective C++ 笔记】构造/析构/赋值

编译器的自动机能 编译器可以暗自为 class 创建 default 构造函数.copy 构造函数.copy assignment 操作符,以及析构函数. 为驳回编译器自动提供的机能,可将成员函数声明为 private 并且不予实现. 例如,如果你打算在一个内含 reference 成员或者 const 成员的 class 内支持赋值操作,必须自己定义 copy assignment 操作符,因为 reference 和 const 变量不可修改. 另外,如果某个 base classes 将

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

章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(2)-读书笔记 <Effective C++>第8章 定制new和delete-读书笔记 条款09:绝不在构造和析构过程中调用virtual函数 你不该在构造和析构函数期间调用virtual函数,因为这样的调用不会带来你预期的结果. (1)在der

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

章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effective C++>第8章 定制new和delete-读书笔记 条款05:了解C++默默编写并调用哪些函数 当C++处理过一个空类后,编译器就会为其声明(编译器版本的):一个拷贝构造函数.一个拷贝赋值运算符和一个析构函数.如果你没有声明任何构造函数,编译器还会声明一个默认构造函数.所有这些函数都被声明为pub

[Effective C++]构造/析构/赋值运算

条款05:了解C++默默编写了并调用了那些函数 请记住: 编译器可以暗自为class 创建default构造函数,copy构造函数,copy assignment 操作符,以及析构函数 class Empty { public: Empty(){...} //default constructor Empty(const Empty& rhs){...} //copy constructor ~Empty(){...} //destructor Empty& operator=(const

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

参考:http://www.cnblogs.com/ronny/p/3740926.html 条款05:了解C++默默编写并调用哪些函数 如果自定义一个空类的话,会自动生成默认构造函数.拷贝构造函数.拷贝赋值函数.析构函数(再次感觉原文翻译的实在是太啰嗦了!). 当成员变量里有const对象或引用类型时,编译器会不能合成默认的拷贝赋值函数:当一个基类把它的拷贝赋值函数定义为private时,它的派生类 也不能生成默认的拷贝赋值函数,因为它无法完成基类成份的赋值. 条款06:若不想使用编译器自动生

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

条款05 : 了解C++默默编写并调用哪些函数 水电费 条款02 : 尽量以const,enum,inline 替换#define 水电费 条款02 : 尽量以const,enum,inline 替换#define 水电费 条款02 : 尽量以const,enum,inline 替换#define 水电费

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

条款05:了解C++默默编写并调用哪些函数 默认构造函数.拷贝构造函数.拷贝赋值函数.析构函数构成了一个类的脊梁,只有良好的处理这些函数的定义才能保证类的设计良好性. 当我们没有人为的定义上面的几个函数时,编译器会给我们构造默认的. 当成员变量里有const对象或引用类型时,编译器会不能合成默认的拷贝赋值函数:当一个基类把它的拷贝赋值函数定义为private时,它的派生类也不无生成默认的拷贝赋值函数,因为它无法完成基类成份的赋值. 条款06:若不想使用编译器自动生成的函数,就该明确拒绝 将拷贝构

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

05.了解C++默默编写并调用哪些函数 编译产生的析构函数时non-virtual,除非这个类的基类析构函数为virtual 成员变量中有引用和const成员时,无法自动生成copy assignment函数 基类将copy assignment操作符声明为private时,编译器拒绝为其derived classes生成一个copy assignment操作符. 06.若不想使用编译器自动生成的函数,就该明确拒绝 将自动生成的默认构造函数,拷贝构造函数,copy assignment声明为pr