《深入探索C++对象模型》第二章:构造函数语意学(上)

来源:互联网 发布:淘宝望远镜 编辑:程序博客网 时间:2024/05/20 18:52

1、Default Constructor的构造操作

default constructor什么时候会被合成?——在被需要的时候......那么是谁需要呢?需要做什么呢?

书中说不是程序需要,而是编译器需要,程序如果有需要,那是程序员的责任。总的来说,对于class X,如果没有任何user-declared constructor,那么会有一个default constructor被隐式的声明出来,将会是一个trivial的。下面我们来讨论一下notrivial constructor的4种情况:

(1)带有Default Constructor的Member Class Object

即如果一个class没有任何constructor,但是内含一个member object,而后者有default constructor,那么这个class的implicit default constructor就是notrivial,编译器需要为该class 合成出一个default constructor,不过这个合成操作只有在constructor真正需要被调用时才会发生。下面举个例子:

class Foo{ public: Foo(); Foo(int)...}class Bar{ public: Foo foo; char *str;}void foo_bar{    Bar bar;    ...}
被合成的Bar default constructor内含必要的代码,能够调用class Foo的default constructor来处理member Bar::foo,但是这里需要说明的是,它并不产生任何代码来初始化Bar::str。因为初始化Bar::foo是编译器的责任,而初始化Bar::str是程序员的责任。被合成的default constructor看起来可能像这样:

inlineBar::Bar(){    foo.Foo::Foo();}
如果程序员定义了一个构造函数,就像这样:

Bar::Bar(){    str = 0;}
那么Bar::foo怎么办?由于default constructor已经被显示的定义出来了,编译器没法合成第二个。好吧,编译器会采取扩张措施:

如果class A内含一个或以上member class objects,那么class A的每一个constructor必须调用每一个member classe的default constructor。即编译器会扩张已存在的构造函数
(2)带有default constructor的Base Class

和上面那种情况的道理差不多,如果一个没有任何constructor的class派生自一个带有default constructor 的base classes,那么这个derived class的default constructor自然会被视为nontrivial,因为编译器要为base进行初始化,扩张是同样的道理。

(3)带有一个virtual function的class

由于这种情况下,class包含了vptr和vtbl,编译器需要对此负责,所以编译器要么进行合成,要么进行扩张:

a.一个virtual function table会被编译器产生出来

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

(4)带有一个Virtual Base Class的class

虽然virtual base class的实现发在不同的编译器之间有极大的差异,但是有一个共同点,就是必须使得virtual base class在其每一个derived class object中的位置,能够于执行期准备妥当。就是说需要进行偏移操作。

综合来说,C++新手通常有两个误解:

a.任何class如果没有定义default constructor,就会被合成出来

b.编译器合成出来的default constructor会显式设定class内每一个data member的默认值

很遗憾,没有一个是对的。

2、Copy Constructor的构造操作

有三种情况,会以一个object的内容作为另一个class object的初值:

a. X xx = x;

b. void foo(X x); X xx; foo(xx);

c. X foo(){ X xx; return xx; }

大部分情况下,当一个class object 以另一个同类实例作为初值,copy constructor会被调用,这可能会导致一个临时性的class object的产生或导致程序代码的蜕变

(1)Default Memberwise Initialization

如果class没有提供一个explicit copy constructor会怎么样?当class object以相同class的另一个object作为初值,其内部是以所谓的default memberwise initialization完成的。下面以一个例子来讲解:

class String{public:    // ...没有explicit copy constructorprivate:    char *str;    int len;}
一个String object的default memberwise initialization发生在这种情况下:

String noun("book");String verb = noun;
其完成方式就像设定每个members一样:

verb.str = noun.str;verb.len = noun.len;

很容易看出来,这种拷贝存在问题...对指针的拷贝

就像default constructor一样,C++ standard上说,如果class没有声明一个copy constructor,就会有隐式的声明或定义。同样的,copy constructor分为trivial和notrivial两种,决定一个copy constructor是否为trivial的标准在于class是否展现出所谓的bitwise copy semantics。

(2)Bitwise Copy Semantics

在下面的程序片段中:

#include"Word.h"Word("book");void foo(){    Word verb = noun;}
很明显verb是根据noun来初始化。但是在尚未看到class Word的声明的时候,我们不能预测这个初始化操作的程序行为。如果class Word的设计者定义了一个copy constructor那么verb的初始化操作会调用它。如果该class没有定义,那么是否会有一个编译器合成的实例被调用?这就得看该class是否展现bitwise copy semantics而定了。下面举个例子:

class Word{public:    Word(const char *);    ~Word(){ delete []str;}private:    int cnt;    char *str;};
这种情况下并不需要合成出一个default copy constructor,因为上述声明展现了default copy semantics(WHY?),而verb的初始化操作也就不需要以一个函数调用收场(不是很明白,ps:其实就是说不一定会合成,不合成的时候,利用member initialization即可),但是,如果class Word是这样声明的:

class Word{public:    Word(const String&);    ~Word();private:    int cnt;    String str;};
其中String声明了一个explicit copy constructor:

class String{public:    //....    String(const String&);    //....};
这种情况下,编译器必须合成一个copy constructor,以便调用member class String的copy constructor,合成的函数就像这样:

inline Word::Word(const Word& wd){    str.String::String(wd.str);    cnt = wd.cnt;}
(3)不要Bitwise Copy Semantics

什么时候class不展现出bitwise copy semantics呢?有以下四种情况

1、当class内含一个member object而后者的class声明有一个copy constructor时

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

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

4、当class派生自一个继承串链,其中有一个或多个virtual base classe时

前两种情况中,编译器必须将member或者base class的copy constructor调用操作安插到被合成的copy constructor中,下面我们着重探讨一下后两种情况,情况3和4都是涉及到了虚函数表。因为同一个class object的虚函数表都是一样的,所以同类之间拷贝构造是允许bitwise的,而派生类向基类转化时,由于不是属于同类,虚函数表不一样,不能bitwise。

下面我们来总结一下,对于第一小节构造函数比较容易理解,对于第二小节的拷贝构造函数想必会有点晕菜,我当时看的时候,主要是被Memberwise和Bitwise给弄晕了。这篇博文给出了详细的解释http://blog.csdn.net/zssureqh/article/details/7696231。其实就是说Default Memberwise Initialization 和 Bitwise Copy并不是一个集合的,也就是两者是不同层次的概念。Default Memberwise Initialization 是编译器默认的拷贝方式,即对构成对象中的每一个成员数据分别进行赋值或者初始化。从对象的数据成员角度出发,具体到对象的每一个数据成员的操作,编译器通常采用(可以认为就是)bitwise copy操作,也就是说Default Memberwise Initialization底层拷贝大部分是采用bitwise copy来完成,这通常会导致浅拷贝问题。与Default Memberwise Initialization相对的概念是User Defined Initialization,用户自定义拷贝构造函数,也就是我们常说的深拷贝。

OK,上基本上说完了,开始下0.0 fighting!!!






阅读全文
1 0
原创粉丝点击