对《C++对象模型》、《More Effective C++》两书中关于构造以及析构函数的讲解总结:
一.关于default constructors.
“default constructors......在需要的时候被编译出来” 此处是指在编译器需要的时候被编译出来,被合成出来的constructor只执行编译器所需的行动。即使有需要为class合成一个default constructor, 也不会将两个data members val 和 pnext 初始化为0.
class Foo{
public: int val;
Foo * pnext;
};
void foo_bar(){
Foobar;
if(bar.val|| bar.pnext)
//..do something
//..
}
“对于class X, 如果没有任何 user-declared constructor, 那么会有一个 default constructor 被隐式(implicitly)声明出来......一个被隐式声明出来的constructor将是一个trival constructor(没啥用的)”
1. 如果一个class 没有任何constructor, 但它内含一个member object,而后者有default constructor,那么个这个class的implicit default constructor 就是"nontrival",编译器需要为该class 合成一个default constructor. 不过这个合成操作只有在constructor 真正需要被调用时才会发生。 C++ 语言要求以"member objects 在class 中的声明顺序" 来调用各个constructors, 以inline的方式安插在程序代码中。
2. 带有default constructor 的 base class
类似道理,如果一个没有任何constructor的class 派生自一个“带有default constructor”的 base class, 那么这个 derived class 的default constructor 会被视为nonvtrival
3. 带有一个virtual Function 的class
class声明或继承一个virtual function
class 派生自一个继承串链,其中有一个或多个virtual base classes vtbl 以及 vptr
4. 带有virtual Base class 的 class
必须使virtual base class 在其每一个 derived class object 中的位置,能够于执行期准备妥当。
C++新手一般有两个误解:
1. 任何class如果没有定义default constructor,就会被合成出一个来。
2. 比那机器合成出来的default constructor 会显示设定"class 内每一个 data member 的默认值"
二.copy constructor 的构造操作
有三种情况,会以一个object的内容作为另一个class object的初值
1.显示的赋值初始化;
2.object当做参数交给某个函数 ;
3.当函数返回一个class object default memberwise initialization;
把每一个内建的或派生的data member(例如一个指针或一个数组)的值,从某个object 拷贝一份到另一个object上。不过它并不会拷贝其中member class object,而是以递归的方式施行memberwise initialization。
一个class 可用两种方式复制得到,一种是被初始化(copy constructor),另一种是被指定(copy assignment operator)。
决定一个copy constructor是否为trival 的标准在于class 是否展现出所谓的 bitwise copy semantics。
三. member initialization list
在下列情况下,为了让你的程序能够被顺利编译,你必须使用member initialization list:
1. 初始化一个reference member;
2. 初始化一个 const member;
3. 调用一个base class 的 constructor, 而它拥有一组参数;
4. 调用一个member class的constructor, 而它拥有一组参数时。
list中的项目顺序是由class中的members 声明顺序决定的,不是由initialization list中的排列顺序决定的,
class X
{
private:
int i;
int j;
public:
X(int val):j(val), i(j) {}
};
i(j)其实比j(val)更早执行,因为j一开始没有值,所以i(j)执行结果就是i无法预知。
正确的写法 initialization list 的项目被放在explicit user code 之前。
X(int val): j(val) {i = j;}
简单的说,编译器会对initializationlist 一一处理并可能重新排序,members 的声明顺序。它会被安插一些代码到constructor体内,并置于任何explicit user code 之前。
四. 析构语意学
如果class没有定义destructor, 那么只有在class 内含memberobject(抑或class 自己的base class)拥有destructor 的情况下,编译器才会自动合成出一个来。甚至虽然它拥有一个virtualfunction,都不会。
1. destructor 的函数本体首先被执行;
2.如果class 拥有member class object,而后者拥有destructor,那么它们会以其声明顺序的相反顺序被调用;
3.如果object 内含一个vptr,现在被重新设定,指向适当之base class 的virtual table。
继承体系下的对象构造
constructor 可能内含大量的隐蔽码,因为编译器会扩充每一个constructor,扩充程度视class T 的继承体系而定。一般而言,编译器所做的扩充操作如下:
1. member initialization list 中的data members 初始化操作会被放进 constructor 的函数本体,并以members 的声明顺序为顺序;
2,如果一个member并没有出现在memberinitialization list 中,但它有一个default constructor, 那么该default constructor 必须被调用;
3. 在那之前,如果class object 有virtual table pointers, 它们必须被设定为初值,指向适当的 virtual tables;
4.在那之前,所有上一层的base class constructors 必须被调用,以base class 的声明顺序为顺序(与memberinitialization list 中的顺序没有关联);
5.所有的virtual base class constructors 必须被调用,从左到右,从最深到最浅。
虚拟继承
传统的“constructor扩充现象”并没有发生,这是因为virtual base class 的共享性的缘故。
五. 构造函数以及析构函数中内存泄露的问题
void processAdoptions(istream&dataSource)
{
while(dataSource)
{
ALA *pa =readALA(dataSource);
pa->processAdoption();
deletepa;
}
}
每当readALA 被调用,便产生一个新的heap object。如果没有调用delete,这个循环很快便会出现资源泄露的问题。
如果pa->processAdoption抛出一个exception,processAdoption 便会发生一次资源泄露。
简单的解决方法:
voidprocessAdoptions(istream& dataSource)
{
while(dataSource) {
ALA *pa =readALA(dataSource);
try{
pa->processAdoption();
}
}
catch(....) {
deletepa;
throw;
}
delete pa; //如果没有exception 被抛出,也要避免资源泄露
}
}
代码凌乱, 思考如何把delete动作从process Adoptions函数移动到函数内某个局部对象的destructor内。
解决的办法就是,以一个"类似指针的对象"取代指针pa。如此当这个类似指针的对象被自动销毁的时候,我们可以令其destructor调用delete。 行为类似 smart pointers。
(我们必须明智和审慎的使用智能指针,因为至少需要加上 copy constructor, assignment operator 以及指针仿真函数 operator* 和 operator->)....
以auto_ptr 对象取代原始指针,就不需要担心heap objects没有被删除,即使在exception 被抛出的情况下。
void processAdoptions(istream&dataSource)
{
while(dataSource)
{
auto_ptr<ALA>pa(readALA(dataSource)); //pa被声明为auto_ptr<ALA>对象,自动析构
pa->processAdoption(); //循环后不再有delete 语句
}
}
一个对象存放"必须自动释放的资源",并依赖该对象的destructor 释放
void displayInfo(const Information&info)
{
WINDOW_HANDLE w(createWindow());
displayinfo in wondow corresponding to w;
destroyWindow(w);
}
class WindowHandle{
public:
WindowHandle(WINDOW_HANDLE handle):w(handle) {}
~WindowHandle(){destroyWindow(w);}
operatorWINDOW_HANDLE() {return w;}
private:
WINDOW_HANDLE W;
WindowHandle(constWindowHandle&);
WindowHandle&operator=(const WindowHandle);
}
void displayInfo(const Information&info){
WondowHandlew(createWindow());
displayinfo in window vorresponding to w; //坚持把资源封装在对象内,通常可以避免资源泄露
}
在constructor 内阻止资源泄露(resource leak):
BookEntry::BookEntry(const string&name,
const string& address,
conststring& imageFileName,
conststring& audioClipFilename)
:theName(name),theAddress(address),theImage(0), theAudioClip(0)
{
if(imageFile!= " ") {
theImage = new Image(imageFileName);
}
if(audioClipFileName!= ""){
theAudioClip = new AutdioClip(audioClipFileName);
}
}
BookEntry:: ~BookEntry(){
delete the Image; //C++保证删除null指针是安全的
deletetheAudioClip;
}
可是当 newAudioClip(audiocliopFileName) 出现异常,控制权移出BookEntry constructor, 此时BookEntry 的 destructor 不会调用来删除theImage已经指向的对象。 C++只会析构已构造完成的对象。