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

《深度探索C++对象模型》读书笔记(2)。

2011-08-09 16:35 197 查看


  default constructor仅在编译器需要它时,才会被合成出来。

   通常来说,由编译器合成出来的default constructor是没啥用的(trivial),但有以下几种例外:

   (1)带有“Default Constructor”的Member Class Object

   如果一个class没有任何constructor,但它内含一个member object,而后者有default constructor,那么编译器会在constructor真正需要被调用时未此class合成一个“nontrivial”的default constructor.为了避免合成出多个default constructor,解决方法是把合成的default constructor、copy
constructor、destructor、assignment copy operator都以inline方式完成。一个inline函数有静态链接(static linkage),不会被档案以外者看到。如果函数太复杂,不适合做成inline,就会合成出一个explicit non-inline static实体。

   根据准则“如果class A内含一个或一个以上的member class objects,那么class A的每一个constructor必须调用每一个member classes的default constructor”,即便对于用户明确定义的default constructor,编译器也会对其进行扩张,在explicit user code之前按“member objects在class中的声明次序”安插各个member所关联的default
constructor.

 class Dcpey   { public:Dopey(); ... };

class Sneezy  { public:Sneezy(int); Sneezy(); ... };

class Bashful { public:Bashful(); ... };

class Snow_White {

public:

Dopey dopey;

Sneezy sneezy;

Bashful bashful;

// ...

private:

int mumble;

};

Snow_White::Snow_White() : sneezy(1024)

{

mumble = 2048;

}

// 编译器扩张后的default constructor

Snow_White::Snow_White() : sneezy(1024)

{

// 插入member class object

// 调用其constructor

dopey.Dopey::Dopey();

sneezy.Sneezy::Sneezy();

bashful.Bashful::Bashful();

// explicit user code

mumble = 2048;

}


关键字: malloc wxWidgets OpenGL 多态性 doxygen

《深度探索C++对象模型》读书笔记(2)。

   (2)“带有Default Constructor”的Base Class

   如果一个没有任何constructors的class派生自一个“带有default constructor”的base class,那么这个derived class的default constructor会被视为nontrivial,并因此需要被合成出来。

   需要注意的是,编译器会将这些base class constructor安插在member object之前。

   (3)“带有一个Virtual Function”的Class

   另有两种情况,也需要合成出default constructor:

   (a)class声明(或继承)一个virtual function

   (b)class派生自一个继承串链,其中有一个或更多的virtual base classes

   以下面这个程序片段为例:

 class Widge {

public:

virtual void flip() = 0;

// ...

};

void flip(const Widge& widge) { widge.flip(); }

// 假设Bell和Whistle都派生自Widge

void foo()

{

Bell b;

Whistle w;

flip(b);

flip(w);

}


   下面两个扩张操作会在编译期间发生:

   1.一个virtual function table会被编译器产生出来,内放class的virtual functions地址;

   2.在每一个class object中,一个额外的pointer member(也就是vptr)会被编译器合成出来,内含相关的class vtbl的地址。

   此外,widge.flip()的虚拟引发操作(virtual invocation)会被重新改写,以使用widge的vptr和vtbl中的flip()条目。

 // widge.flip()的虚拟引发操作的转变

(*widge.vptr[1])(&widge)


   为了让这个机制发挥功效,编译器必须为每一个Widge(或其派生类)之object的vptr设置初值,放置适当的virtual table地址。

关键字: malloc wxWidgets OpenGL 多态性 doxygen

《深度探索C++对象模型》读书笔记(2)。

   (4)“带有一个virtual Base Class”的Class

 class X { public: int i; };

class A : public virtual X { public: int j; };

class B : public virtual X { public: double d; };

class C : public A , public B{ public: int k; };

// 无法在编译时期决定出pa->X::i的位置

void foo(const A* pa) { pa->i = 1024; }

main()

{

foo(new A);

foo(new C);

// ...

}


   编译器需要在derived class object中为每一个virtual base classes安插一个指针,使得所有“经由reference或pointer来存取一个virtual base class”的操作可以通过相关指针完成。

 // 可能的编译器转变操作

// _vbcX表示编译器所产生的指针,指向virtual base class X

void foo(const A* pa) ...{ pa->_vbcX->i = 1024; }


   小结:在合成的default constructor中,只有base class subobjects和member class objects会被初始化,所有其它的nonstatic data member都不会被初始化。

  有三种情况会以一个object的内容作为另一个class object的初值,即object赋值、object参数传递、object作为函数返回值。

   如果class没有提供一个explicit copy constructor,其内部是以所谓的default memberwise initialization手法完成的,也就是把每一个内建的或派生的data member(例如一个指针或一个数组)的值,从某个object拷贝一份到另一个object身上,不过它并不会拷贝其中的member class object,而是以递归的方式施行memberwise
initialization. copy constructor仅在必要的时候(class不展现bitwise copy semantics)才由编译器产生出来。

关键字: malloc wxWidgets OpenGL 多态性 doxygen

《深度探索C++对象模型》读书笔记(2)。

   已知下面的class Word声明:

 // 以下声明展现了bitwise copy semantics

class Word {

public:

Word( const char* );

~Word(){ delete []str; }

// ...

private:

int cnt;

char *str;

};


   对于上述这种情况,并不需要合成出一个default copy constructor,因为上述声明展现了“default copy semantics”。

   然而,如果class Word是这样声明:

 // 以下声明未展现出bitwise copy semantics

class Word {

public:

Word( const String& );

~Word();

// ...

private:

int cnt;

String& str;

};


   在这种情况下,编译器必须合成出一个copy constructor以便调用member class String object的copy constructor:

 // 一个被合成出来的copy constructor

inline Word::Word(const Word& wd)

{

str.String::String(wd.str);

cnt = wd.cnt;

}


   一个class不展现出“bitwise copy semantics”的四种情况:(1)当class内含一个member object而后者的class声明有一个copy constructor时(无论是被明确声明或被合成而得)

   (2)当class继承自一个base class而后者存在有一个copy constructor时

   对于前两种情况,编译器必须将member或base class的“copy constructor调用操作”安插到被合成的copy constructor中。

   (3)当class声明了一个或多个virtual functions时

   由于编译器要对每个新产生的class object的vptr设置初值,因此,当编译器导入一个vptr到class之中时,该class就不再展现bitwise semantics了。

关键字: malloc wxWidgets OpenGL 多态性 doxygen

《深度探索C++对象模型》读书笔记(2)。

   当一个base class object以其derived class的object内容做初始化操作时,其vptr复制操作必须保证安全,而如果依旧采用bitwise copy的话,base class object的vptr会被设定指向derived class的virtual table,而这将导致灾难。

   (4)当class派生自一个继承串链,其中有一个或多个virtual base classes时当一个class object以其derived classes的某个object作为初值时,为了完成正确的virtual base class pointer/offset的初值设定,编译器必须合成一个copy constructor,安插一些码以设定virtual base class
pointer/offset的初值,对每一个member执行必要的memberwise初始化操作,以及执行其他的内存相关操作。在这种情况下,简单的bitwise copy所做的就远远不够了。

   ***优化***

   (1)在使用者层面做优化定义一个“计算用”的constructor:

 X bar(const T &y,const T &z)

{

X xx;

// ... 以y和z来处理xx

return xx;

}


   上述constructor要求xx被“memberwise”地拷贝到编译器所产生地_result之中。故可定义如下的constructor,可以直接计算xx的值:

 X bar(const T &y,const T &z)

{

return X(y,z);

}


   (2)在编译器层面做优化比较以下三处初始化操作:

 X xx0(1024);

X xx1 = X(1024);

X xx2 = (X)1024;


   其中,xx0是被单一的constructor操作设定初值:

   xx0.X::X(1024);

   而xx1和xx2则是调用两个constructor,产生一个暂时性的object并设以初值1024,接着将暂时性的object以拷贝建构的方式作为explicit object的初值,最后还针对该暂时性object调用class X的destructor:

关键字: malloc wxWidgets OpenGL 多态性 doxygen

《深度探索C++对象模型》读书笔记(2)。

 X _temp0;

_temp0.X::X(1024);

xx1.X::X(_temp0);

_temp0.X::~X();


   由此可以看出,编译器所做的优化可导致机器码产生时有明显的效率提升,缺点则是你不能安全地规划你的copy constructor的副作用,必须视其执行而定。

   那么,究竟要不要copy constructor?copy constructor的应用,迫使编译器多多少少对你的程序代码做部分优化。尤其是当一个函数以传值(by value)的方式传回一个class object,而该class有一个copy constructor时,这将导致深奥的程序转化。

   举个简单的例子,不管使用memcpy()或memset(),都只有在“classes不含任何由编译器产生的内部members”时才能有效运行。而如下的class由于声明了一个virtual function,编译器为其产生了vptr,此时若使用上述函数将导致vptr的初值被改写。

 class Shape ...{

public:

// 这会改变内部的vptr

Shape() { memset(this,0,sizeof(Shape)); };

virtual ~Shape();

// ...

};

// 扩张后的constructor

Shape::Shape()

{

// vptr必须在使用者的代码执行之前先设定妥当

_vptr_Shape = _vtbl_Shape;

// memset会将vptr清为0

memset(this,0,sizeof(Shape));

}


   ***成员的初始化队伍***

   下列情况中,为了让你的程序能够被顺利编译,你必须使用member initialization list:

   (1)当初始化一个reference member时;

   (2)当初始化一个const member时;

   (3)当调用一个base class的constructor,而它拥有一组参数时;

   (4)当调用一个member class的constructor,而它拥有一组参数时。

关键字: malloc wxWidgets OpenGL 多态性 doxygen

《深度探索C++对象模型》读书笔记(2)。

   成员初始化列表有时可带来巨大的性能提升,不妨看看下面一组对比:

 class Word {

String _name;

int _cnt;

public:

Word() {

_name = 0;

_cnt = 0;

}

};

// 其constructor可能的内部扩张结果

Word::Word()

{

// 调用String的default constructor

_name.String::String();

// 产生暂时性对象

String temp = String(0);

// “memberwise”地拷贝_name

_name.String::operator=(temp);

// 摧毁暂时性对象

temp.String::~String();

_cnt = 0;

}


   若使用成员初始化列表:

 // 较好的方式

Word::Word : _name(0)

{

_cnt = 0;

}

Word::Word()

{

// 调用String(int) constructor

_name.String::String(0);

_cnt = 0;

}


   需要注意的是,成员初始化列表中的项目是依据class中的member声明次序,一一安插到explicit user code之前的。(因此,对于表达式两边均出现member的情形要特别小心)

   下面就给出错误实例:

 class X {

int i;

int j;

public:

// i比j先被赋值,而此时j尚未有初值

X(int val) : j(val),i(j)

{ }

...

};


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