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

1:一个空类,如果你自己没声明,编译器就会为它声明(编译器版本的)一个copy构造函数、一个copy assignment操作符和一个析构函数。此外如果你没有声明任何构造函数,编译器也会为你声明一个default构造函数。所有这些函数都是public且inline的。

2:只有当这些函数被调用时,它们才会被编译器创建出来。

3:编译器生成的default构造函数和析构函数主要是给编译器一个地方用来放置“藏身幕后”的代码,比如用base classes和non-static成员变量的构造函数和析构函数。至于copy构造函数和copy assignment操作符,编译器创建的版本只是单纯的将来源对象的每一个non-static成员变量拷贝到目标对象。

4:编译器创建的析构函数是个non-virtual,除非这个class的base class自身声明有virtual析构函数。

5:以下情况下,编译器会拒绝为class生出operator=:

a、类中具有引用成员或者const 常量成员:

template<class T>
class NamedObject {
public:
NamedObject(std::string& name, const T& value);
...
private:
std::string& nameValue; // this is a reference
const T objectValue; // this is const
};

引用和常量必须在定义时进行初始化,不支持赋值操作。因此编译器拒绝为这样的类创建operator=函数;

如果你打算在一个包含reference成员或const成员的class内支持赋值操作,你必须自己定义copy assignment操作符。

b、如果某个base class将copy assignment操作符声明为private,编译器也拒绝为其derived class生成一个copy assignment操作符。

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

1:如果不希望class支持复制初始化或者赋值操作,因为不定义copy构造函数和copy assignment操作符,编译器会自动创建一个,因此不定义这俩函数达不到这个目的。

2:因为编译器创建的函数都是public的,为了阻止这些函数被创建出来,可以将copy构造函数或copy assignment操作符声明为private。这样便阻止了编译器创建这些函数,而且类的用户也无法调用它们。

3:上面的做法还是有漏洞,因为类的成员函数和友元函数还是可以调用你的private函数。这种情况下,可以仅仅声明而不去定义它们。这种情况下,如果有成员函数或友元函数调用它们的话,将会产生一个连接错误。

因此,将复制构造函数和赋值操作符声明为private且不去定义它们,当类的用户企图拷贝时,编译器会阻止他;如果在成员函数或友元函数中这么做,连接器会发出抱怨。

4:将连接期错误移至编译期是可能的(而且那时好事,毕竟越早侦测出错误越好),只要定义一个Uncopyable类,并将自己的类继承该类就好:

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

任何人(包括成员函数或友元函数)尝试拷贝Uncopyable类的派生类对象时,编译期便试着生成一个copy构造函数和一个copy assignment操作符。这些函数的编译器生成版会尝试调用其base class的对应函数,那些调用会被编译器拒绝,因为其base class的拷贝函数是private。

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

1:当derived class对象经由一个base class指针删除,而该base class带着一个non-virtual析构函数,则其结果是未定义的。实际执行时通常发生的是对象的derived成分没被销毁,而其base class成分通常会被销毁,于是造成一个诡异的“局部销毁”对象。

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

3:如果class不含virtual函数,通常表示它并不愿意被用做一个base class。当class不企图被当作base class时,令其析构函数为virtual往往是个馒主意。

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

因此,无端的将某个class的析构函数声明为virtual,会增加对象的体积(vptr)。因此许多人的心得是:只有当class内含至少一个virtual函数,才为它声明virtual析构函数。

4:标准string,以及所有STL容器如vector,list,set,map等等,都不virtual析构函数,因此,不应该将它们当做base class。

5:如果抽象基类声明了virtual析构函数,则必须为它提供一份定义:

class AWOV {
public:
virtual ~AWOV() = ; // declare pure virtual destructor
}; AWOV::~AWOV() {} // definition of pure virtual dtor

这是因为,派生类继承该抽象基类后,派生类对象销毁时,会首先调用派生类的析构函数,然后是基类的析构函数。因此编译器会在AWOV的derived classes的析构函数中创建一个对~AWOV的调用,所以必须提供一份定义,否则会连接错误。

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

C++并不禁止析构函数吐出异常,但它不鼓励这么做。如果某个类的析构函数有可能抛出异常,则要么:抛出异常时直接调用abort退出程序;要么抛出异常时吞下异常,仅记录日志。

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

1:不要再构造函数和析构函数中,调用virtual函数。

2:在base class构造期间,如果构造函数中调用了virtual函数,即使当前正在构造derived class(构造派生类对象时,需要首先构造其基类部分),virtual函数也是base class中的版本。也就是说;在base class构造期间,virtual函数不是virtual函数。

在derived class对象的base class构造期间,对象的类型是base class而不是derived class。不只virtual函数会被编译器解析至(resolve to)base class,若使用运行期类型信息(runtime type information,例如dynamic_cast和typeid),也会把对象视为base class类型。

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

4:确定你的构造函数和析构函数都没有调用virtual函数,而它们调用的所有函数也都要服从这一约束。

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

1:赋值时,可以将其写成连锁形式:x = y = z = 15;赋值采用右结合律,因此这个表达式等价于:x = ( y = ( z = 15 ) );

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

Widget& operator=(const Widget& rhs)   // return type is a reference to
{ // the current class
...
return *this; // return the left-hand object
}

3:这个协议不仅适用于标准的赋值形式,也适用于所有赋值相关运算,比如+=。

11:在operator=中处理“自我赋值”

1:“自我赋值”发生在对象被赋值给自己时,不要认定客户绝不会那么做,而且自我赋值并不总是那么可被一眼辨识出来,例如:a[i] = a[j];这条语句中,如果i和j相同,这便是自我赋值;再比如:*px = *py;如果px和py恰巧指向相同,这也是自我赋值。

2:“自我赋值”时,可能会掉进“在停止使用资源之前意外释放了它”的陷阱。比如:

Widget& Widget::operator=(const Widget& rhs)
{
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}

这里的自我赋值问题是,operator=函数内的*this和rhs有可能是同一个对象。果真如此delete就不只是销毁当前对象的bitmap,它也销毁rhs的bitmap。

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

Widget& Widget::operator=(const Widget& rhs)
{
if (this == &rhs) return *this; // identity test: if a self-assignment,
// do nothing
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}

3:这个新版本仍然存在异常方面的麻烦。更明确地说,如果”new Bitmap”导致异常(不论是因为分配时内存不足或因为Bitmap的copy构造函数抛出异常),Widget最终会持有一个指针指向一块被删除的Bitmap。

令人高兴的是,让operator=具备“异常安全性”往往自动获得“自我赋值安全”的回报。因此愈来愈多人对“自我赋值”的处理态度是倾向不去管它,把焦点放在实现“异常安全性”上。例如,我们只需注意在复制pb所指东西之前别删除pb:

Widget& Widget::operator=(const Widget& rhs)
{
Bitmap *pOrig = pb; // remember original pb
pb = new Bitmap(*rhs.pb); // make pb point to a copy of *pb
delete pOrig; // delete the original pb
return *this;
}

现在,如果”new Bitmap”抛出异常,pb保持原状。即使没有证同测试,这段代码还是能够处理自我赋值,因为我们对原bitmap做了一份复件、删除原bitmap、然后指向新制造的那个复件。它或许不是处理“自我赋值”的最高效办法,但它行得通。

4:在operator=函数内确保代码不但“异常安全”而且“自我赋值安全”的一个替代方案是,使用所谓的copy and swap技术。

它是一个常见而够好的operator=撰写办法:

Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs); // make a copy of rhs's data swap(temp); // swap *this's data with the copy's
return *this;
}

或者,可能更常见的是下面这种写法:

Widget& Widget::operator=(Widget rhs)
{
swap(rhs);
return *this;
}

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

1:如果自己写复制构造函数或赋值操作符而不使用编译器的版本,则需要注意的是:如果你为class添加一个成员变量,你必须同时修改复制构造函数和赋值操作符函数(你也需要修改class的所有构造函数,以及任何非标准形式的operator=(比如+=))。如果你忘记,编译器不太可能提醒你。

2:任何时候只要你承担起“为derived class撰写copying函数”的重责大任,必须很小心地也复制其base class成分。那些成分往往是private,所以你应该让derived class的copying函数调用相应的base class函数。

Effective C++: 02构造、析构、赋值运算的更多相关文章

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

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

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

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

  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++默默编写并调用哪些函数 编译器可以暗自为class创建default构造函数.copy构造函数.copy assignment操作符,以及析构函数. 1. default构造函 ...

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

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

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

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

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

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

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

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

随机推荐

  1. Luogu P1278 单词游戏(dfs)

    P1278 单词游戏 题意 题目描述 \(Io\)和\(Ao\)在玩一个单词游戏. 他们轮流说出一个仅包含元音字母的单词,并且后一个单词的第一个字母必须与前一个单词的最后一个字母一致. 游戏可以从任何 ...

  2. Leetcode80. Remove Duplicates from Sorted Array II删除排序数组中的重复项2

    给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成. 示例  ...

  3. PHP CURL header 设置HOST主机头进行访问并 POST提交數據

    $host = array("Host: act.qzone.qq.com");// 域名不帶http://$data = array(            'aa' => ...

  4. c++新特性实验(2)类型特性

    1. 基本类型 1.1 增加 long long long long int signed long long signed long long int unsigned long long unsi ...

  5. ECMAScript 6 (浅显入门)

    1.let:ES6 新增了let命令,用来声明变量.它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效. var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为unde ...

  6. win10 基础上装一个 ubuntu 双系统

    1.准备一个空闲的分区: 1)确定每个磁盘都已经是 EFI 分区格式,如果不是,可以使用分区工具,将分区都转成EFI  (例如 DiskGenius 工具挺好) 2)选择一个剩余空间较大的压缩空间,压 ...

  7. 汉诺塔III HDU - 2064

    汉诺塔III HDU - 2064   约19世纪末,在欧州的商店中出售一种智力玩具,在一块铜板上有三根杆,最左边的杆上自上而下.由小到大顺序串着由64个圆盘构成的塔.目的是将最左边杆上的盘全部移到右 ...

  8. 【JZOJ5363】【NOIP2017提高A组模拟9.14】生命之树 Trie+启发式合并

    题面 45 在比赛中,我只想到了45分的暴力. 对于一个树中点对,相当于在他们的LCA及其祖先加上这个点对的贡献. 那么这个可以用dfs序+树状数组来维护. 100 想法 我想到了可能要用trie树来 ...

  9. 自定义确定框(confirm)

    1.先引入 confirm.css @charset "UTF-8"; lq-alert { width: 100%; height: 100%; background: rgba ...

  10. UVA1204 Fun Game

    Fun Game https://odzkskevi.qnssl.com/8d698323a1e07d605cdeea708ee8a01d?v=1508703139 [题解] 不难发现如果一个串的原串 ...