条款05:了解C++默默编写并调用哪些函数(Know what functions C++ silently writes and calls).
内容:
有人提出了一个问题:如果我写了一个空类,里面什么都没有,什么功能都没有实现,这样的
类合法么?我能用它么?比如:
class Null{};
也许你也有相同的问题,而我的答案是----->当然合法,你完全可以像一般类一样用它.提出
这个问题的人把我们今天讨论的话题给引导出来了:编译器到底默默地对我们自己写的类做了些
什么,进行了哪些"暗箱操作"?
当你写了个新类时,你有可能忘记写它的构造函数、析构函数、拷贝构造、赋值函数,会不
会编译不过?Don't worry! 编译器会为你提供这些函数的默认实现的.比如上面的Null类,编译
器会自动将它转换为如下等价代码:
class Null{
public:
Null(){...}
~Null(){...}
Null(const Null& newNull){...}
Null& operator = (const Null& newNull){...}
};
对于编译器默认构造函数,很多情况下它不会自动为你初始化内置型变量的值,这点很可怕,
常常会导致一些莫名其妙的情况甚至是系统无迹象地崩溃,最好的做法还是老老实实的显式提
供构造函数为上上之策,呵呵,对于一些非内置型变量你就必须提供它的默认构造函数,否则编
译不过.
提供的默认析构函数都是non-virtual,除非这个class的基类的析构函数为virtual类型的.
拷贝构造函数(copy constructor)其实和一般的构造函数具有相似的功能只不过它的参数
是另一个该类的引用对象,它的初始化过程通过调用成员变量的copy constructor并传递右操
作数(那个对象参数)的成员变量值来完成初始化过程的.我们来看下面这个例子,这里我写了个
类BookEntry:
class BookEntry{
public:
BookEntry(string& title,double price,string& author)
:title_(title),price_(price),author_(author){
}
...
private:
string title_,author_;
double price_;
};
//test.cpp
BookEntry firstBook("Effective C++",65.0,"Scott Meyers");
BookEntry secondBook(firstBook); //copy constructor
BookEntry thirdBook=secondBook; //notice: here call copy constructor too.
到这里生成的三个对象保持了相同的数据拷贝,这里我们注意一下最后一条语句调用的是
copy constructor还是对它进行assign操作符运算,这里不要认为是调用了assign操作符,因
为编译器优化的结果,编译器不需要先初始化以后再调用assign来完成一个新对象的构造过程,
它可以直接调用copy构造函数直接完成,如果连这点都不能做到的话,那你这款这编译器也够
糟糕的了!呵呵.
最后我们来谈一下赋值构造运算(copy assign operator),它的行为基本上和copy
constructor如出一辙,到这里也许你会产生一个看似很有天分的想法:既然编译器为我们提供
了copy assign operator的默认实现,那么我们就没有必要为自己的新类来实现这个操作了!
这样看起来好像是省了我们不少麻烦,但是实际上没那么简单,我不得不提醒你一下:别把世界
想的太美好!在有些情况之下你如果不去提供实现题而单单依靠他的默认实现的话,往往会带来
一些连你自己都想象不到的问题,甚至有的连编译也通不过,这是为什么呢?一般而言只有当生
成的代码合法且有机会证明它有意义的情况下,copy assign才表现的是良好的,倘若这两个条
件中有一个不符合,那么作为编译器来说它会怎么做?当然拒绝这个操作了哦!下面我来举个简
单的例子来说明这个问题:
我还是用上面的这个BookEntry类,稍微修改一下来达到我们的目的,呵呵:
class BookEntry{
public:
BookEntry(string& title,double price,...)
:title_(title),price_(price),...{
}
...
private:
string& title_;
double price_;
};
BookEntry oldBook("prison break",25.5,...);
BookEntry newBook("Unix network programming",43.0);
newBook = oldBook; // compiler error: 'operator =' function is unavailable in 'BookEntry'
哒哒哒哒,问题来了吧?书上解释的原因我看了好几遍也没看懂,又请教了高手才弄懂,那么
我尽可能的来解释它,对于"内含reference"的类的copy assign运算符"=",newBook = oldBook;从这
条语句上来看是说newBook对象成员值被修改为oldBook成员的值,而这里BookEntry的成员
title_是一个引用,如何修改引用?这里的话编译器遇到了一个难题,因为修改引用可以用两中理
解:一是将其重新reference一个新的对象,显然这样不符号C++语法,C++不允许"让reference改
指不同的对象";二是修改其引用对象的值.原则上这两种可能都会出现,客户到底是哪一种意图
呢?编译器不能做出判断,于是它两个肩膀一耸,无能为力,不去为其提供默认copy assign操作,
那我们要用操作符'='怎么办?你必须自己定义copy assign操作的实现,呵呵!
到这里我要对copy assign操作中的特殊的情况进行总结一下,一是对于"含reference"的类,如
果你想用'='操作符的话,不好意思,你得自定义它的实现体;二是对于"含const对象"的类,编译器的
反应当然是一样的,修改const对象本来就是非法的;还有一种情况是如果基类base class中
operator=被声明为private那么编译器就拒绝为其子类derived class提供默认的copy assign
操作实现体.这三条大家要注意一下.好了,今天写的够累的了!
请记住(最后一条是我自己加的,呵呵):
■ 编译器可以暗自为class创建default构造函数,copy构造函数,copy assignment操作符以
及析构函数.
■ 请一定要清楚编译器为我们做了什么?在一些情况下,不要去麻烦编译器为我们自动生成
的东西,因为这些东西不一定就是我们想要的,compiler is not god!,我们最好自己去实现它们
,believe yourself!