您的位置:首页 > 编程语言 > C语言/C++

Effect C++ 笔记 【2 Constructors Destructors and Assignment Operators】

2010-11-02 23:47 531 查看

2 赋值/析构/赋值运算

一个class, 一般都有一个或多个【构造函数】,一个【析构函数】,一个【拷贝赋值操作符】(copy assignment “=")。 他们是类的基础操作,必须确保他们的行为正确。

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

一个empty类,如果自己没声明,编译器会声明一个构造函数,一个【析构函数】,一个【拷贝构造函数】,和一个【拷贝赋值操作符】。 所有这些都是 public 且 inline的。

就像,如果写一个 class empty { }; // 就相当于写下了:

class Empty {
public:
Empty() {...};                           //default构造函数
Empty(const Empty& rhs) {...};           //copy构造函数
~Empty() {...}                           //析构函数。 注意是否virtual 。
Empty& operator=(const Empty& rhs) {...};//拷贝赋值操作符
};


当这些函数被需要(调用)时,才被编译器创建出来。

default构造函数和析函数主要是给编译器一个地方用来放置”藏身幕后“的代码,像是调用base classes和non-static成员变量的构造函数和析构函数。

编译器产生的析构函数是 non-virtual

, 除非这个类的 base class 自身声明有 virtual 析构函数



拷贝构造函数 和 拷贝赋值 只是单纯的将来源对象的每一个 non-static 成员变量拷贝到目标对象(内置类型编译器直接复制,自定义类型调用其拷贝构造函数)。

但是,有几种情况编译器会拒绝编译这个赋值动作:

1. class 内含有 reference成员。 C++不允许”让 reference 改指向不同对象“ 。 // 不太理解什么情况下class内会有const成员和reference成员

2. class 内含有 const 成员。

3. 基类将 拷贝赋值操作符 声明为 private。

TIps

: 1. 注意析构函数的 virtual

2. 拷贝构造/拷贝赋值 注意 class中 是否有 引用和const 成员。

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

比如,你不声明copy构造函数,那编译器就会为你制造一个。 如果你不想要这种自动生成的函数,就需要使用下面的方法:

因为所有编译器产出的函数都是public , 为了阻止这些函数被创建,你得自行声明它们(而不实现),并声明为 private, 使你得以成功阻止人们调用它。(private 成员函数只能在该类定义内部使用。对象不能调用) C++ iostream程序库中阻止copying行为就是这么干的。

还可以,在一个专门阻止copying动作和设计的 base class内,声明这种函数。然后,让我们需要这种行为的类继承它。// uncopyable class 的运用比较微妙。设计多继承等等,有需要看书吧。

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

局部销毁问题:当derived class 对象经由一个 base class 指针被删除,而该 base class 带着一个 non-virtual 析构函数,其结果未有定义——执行时通常是 对象的 derived 成分没被销毁。

任何class只要带有 virtual 函数,几乎确定应该有一个 virtual 析构函数

。 但给不含virtual函数的class,定义 虚析构函数 是个馊主意。 // class不含virtual ,通常表示它并不意图做一个base class。

[ 要是先出virtual函数,对象必须携带某些信息:C++实现virtual函数,对象用一个(virtual table pointer)指针指出调用哪个虚函数。vptr指向一个由函数指针构成的数组 vtbl (virtual table); 每一个带有虚函数的class都有一个相应的vtbl。]

// 如果一个class 含有两个int成员(32bit),加入vptr后对象体积可能会增加50%~100%。这个类就不再具有移至性。

【心得】: 当class内含至少一个virtual函数,才为它声明 virtual 析构函数

【当心】:class不带 virtual 函数,你用它作为基类,可能会导致错误
(比如标准string不含任何virtual函数,但程序员错误的用它作为基类派生一个 specialString 类,可能会导致局部销毁的问题)

【虚析构方式】: 最深层的那个class析构函数最先被调用,然后逐级向上找base的析构。 如果用 pure virtual(纯虚函数的 析构),记得必须为pure virtual提供一份定义,否则链接报错。

Tips: 1. 带多态性的 base classes 应该声明一个 virtual 析构函数。

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

条款08:别让异常逃离析构函数 //这个理解不深

两个异常同时存在的情况下,程序若不结束执行 会导致不明确行为。

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

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

在 base class 构造期间, virtual 函数不是 virtual的。(构造base class期间,derived class 成员变量尚未初始化。如果此期间virtual下降到derived层,要知道derived class的函数几乎必然取用local成员变量)

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

内置类型和标准程序库提供的类型都这么做的。

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

确保当对象自我赋值时 operator= 有良好的行为。 方式包括: 1. 比较来源和目标对象地址;2. 精心调整语句顺序;3. copy and swap。

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

任何时候只要为“derived class 撰写 copying 函数”,必须小心也复制其base class成分。 那些成分往往是private,所以你无法直接访问。应该让derived class 的 copying函数调用相应的base class 函数:

Derived::Derived(const Derived& rhs)
:   Base(rhs)                // 初始化列表,初始化自定义类型对象 (这里Base是个对象)
{
init();                     //  初始化其他基本数据类型
}
Derived& Derived::operator=(const Derived& rhs)
{
Base::operator=(rhs);  //对base的成分进行赋值动作
return *this;
}


Tips:

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

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

3. 拷贝赋值和拷贝构造函数 不要互相调用。如有重复机能,也放到第三个函数中



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: