C++对象模型学习——构造函数语意学
2016-07-04 00:00
525 查看
摘要: 通过《深入探索C++对象模型》第二章学习C++
对于Class X,如果没有任何user-declared constructor,那么会有一个default constructor被
隐式(implicitly)声明出来……一个隐式声明出来的default constructor将是一个trivial(无用
的)constructor……
如下例子:
上面代码的语义是要求Foo有一个default constructor,可以将它的两个members初始化为
0。而上面代码并不会合出一个default constructor。其见差距在于一个是程序需要,一个是编译
器需要。程序如果有需要,那是程序员的责任,本例中要承担责任的是设计class Foo的人。
只有当编译器需要它时,才会合出一个default constructor,被合成出的constructor只执行编
译器所需的行动。也就是说,即使需要位class Foo合成一个default constructor,那个
constructor也不会将两个data members val和pnext初始化为0。为了上一段代码正确执
行,class Foo的设计者必须提供一个显示的default constructor,将两个members适当地初始
化。
下面4小节分别讨论nontrivial default constructor的4种情况:
constructor,那么这个class的implicit default constructor就是“nontrivial”,编译器需要为该class
合成出一个default constructor。不过这个合成操作只有在constructor真正需要被调用时才会发
生。
不同的编译模块通过合成default constructor、destructor、assignment copy operator都以
inline方式完成,来避免编译器合出多个default constructor。一个inline函数有静态链接(static
linkage),不会被文件以外者看见。如果函数太复杂,不适合做成inline,就会合成一个
explicit non-inline static实例。
如下例子,编译器为class Bar合成一个default constructor:
可以通过编译器生成的汇编代码看到,的确生成了Bar的default constructor,并且调用了
Foo的default constructor,但它并不产生任何初始化Bar::str。初始化Bar::foo是编译器的责任,
而初始化Bar::str则是程序员的责任。
如果class A内含一个或一个以上的member class objects,那么class A的每一个constructor
必须调用每一个member classes的default constructor。编译器会扩张已存在的constructors,在
其中安插一些代码,使得user code被执行之前,先调用必要的default constructors。如下例
子,在class Bar中加入程序员定义的default constructor:
可以从上面汇编代码看出,的确Bar的default constructor函数被扩张,在开始调用了
Foo::Foo()。
对于有多个class member objects都要求constructor初始化操作,C++语言要求以member
class在class中的声明顺序来调用各个constructors。例如:
可以从汇编代码看到Snow_White的default Constructor的确先调用了Dopey::Dopey(),
Sneezy::Sneezy(), Bashful::Bashful()。
而如果给Snow_White加上default Constructor,如下:
可以根据汇编代码看到的确Snow_White的default constructor被扩张了。
那么这个derived class的default constructor会被视为nontrivial,并因此需要被合成出来。
可以通过汇编代码看到,Snow_White的default constructor被合成出并调用了Dopey的
default constructor。
1)class声明(或继承)一个virtual function。
2)class派生自一个继承串链,其中有一个或更多的virtual base classes。
下面两个扩展行动在编译器发生:
1)一个virtual function table会被编译出来,内放class的virtual functions地址。
2)在每一个class object中,一个额外的pointer member(也就是vptr)会被编译器合成出来,
内含相关class vtbl的地址。
为了让这个机制发挥功效,编译器必须为每一个Widget(或其派生类的)object的vptr设定
初值,放置适当的virtual table地址。在定义的每一个default constructor和constructor中安插一
些代码。如下:
可以看到编译器为A,B,C合成了不同的默认构造函数。
最明显的一种情况当然就是对一个object做显式的初始化操作:
另外两种情况是当object被当做参数交给某个函数时:
以及当函数传回一个class object时:
memberwise initializtion手法完成的,也就是把每一个内建的或派生的data member(例如一个
指针或一个数组)的值,从某个object拷贝一份到另一个object身上。不过它不会拷贝其中的
member class object,而是递归的方式施行memberwise initialization。例如:
一个String object的default memberwise initialization发生在这种情况之下:
如果一个String object被声明为另一个class的member,像这样:
那么一个Word object的default memberwise initialization会拷贝其内建的member _occurs,
然后再于String member object _word身上递归实施memberwise initialization。
一个class object可用两种方式复制得到,一种是被初始化,另一种是被指定
(assignment)。从概念上而言,这两个操作分别是以copy constructor和copy assignment
operator完成的。
很明显verb是根据noun来初始化。如果class Word的设计者定义了一个copy
constructor,verb的初始化操作会调用它。但如果class没有定义explicit copy constructor,那么
编译器是否会合成一个调用实例,这就得视该class是否展现“bitwise copy semamtics”而定。
这种情况下不需要合成出一个default copy constructor,因为上述声明展现了“default copy
semantics”,而verb的初始化操作也就不需要以一个函数调用收场。
这种情况下,编译器必须合成一个copy constructor,以便调用member class String object
的copy constructor:
1)当class内含一个member object而后者的class声明有一个copy constructor时(不论是被
class设计者显式地声明,就像前面的String那样;或是被编译器合成,像class Word那样)。
2)当class继承自一个base class而后者存在一个copy constructor(也是不论是被显式声明
或是被合成而得)。
3)当class声明了一个或多个virtual functions时。
4)当class派生自一个继承串链,其中有一个或多个virtual base classes时。
下面是后两种情况的讨论。
汇编代码如下:
下面是虚表:
从main函数的调用过程可以看出yogi和winnie调用的Bear虚表所指的Bear::animate()函数和
Bear::draw()函数。
当一个base class object以其derived class的object内容做初始化操作时,其vptr复制操作也
必须保证安全,如下:
可以看出franny的vptr指向ZooAnimal的virtual table,而非Bear的virtual table(它由yogi的
vptr指出)。
那么也会使“bitwise copy semantics”失效。
我们看到在4种情况下,class不再保持“bitwise copy semantics”,而且default copy
constructor如果未被声明的话,会被视为nontrivial。如果缺乏一个已声明的copy constructor,
编译器为了正确处理“以一个class object作为另一个class object的初值”,必须合成出一个copy
constructor。
一、Defalut Constructor的构造函数
C++ Standard [ISO-C++95]的Section12.1这么说:对于Class X,如果没有任何user-declared constructor,那么会有一个default constructor被
隐式(implicitly)声明出来……一个隐式声明出来的default constructor将是一个trivial(无用
的)constructor……
如下例子:
#include <iostream> class Foo { public: int val; Foo *pnext; }; int main() { Foo bar; if( bar.val || bar.pnext ) { std::cout << "参数未初始化为0" << std::endl; } else { std::cout << "参数初始化为0" << std::endl; } return 0; }
上面代码的语义是要求Foo有一个default constructor,可以将它的两个members初始化为
0。而上面代码并不会合出一个default constructor。其见差距在于一个是程序需要,一个是编译
器需要。程序如果有需要,那是程序员的责任,本例中要承担责任的是设计class Foo的人。
只有当编译器需要它时,才会合出一个default constructor,被合成出的constructor只执行编
译器所需的行动。也就是说,即使需要位class Foo合成一个default constructor,那个
constructor也不会将两个data members val和pnext初始化为0。为了上一段代码正确执
行,class Foo的设计者必须提供一个显示的default constructor,将两个members适当地初始
化。
下面4小节分别讨论nontrivial default constructor的4种情况:
1、“带有Default Constructor”的Member Class Object
如果一个class没有任何constructor,但它内含一个member object,而后者有defaultconstructor,那么这个class的implicit default constructor就是“nontrivial”,编译器需要为该class
合成出一个default constructor。不过这个合成操作只有在constructor真正需要被调用时才会发
生。
不同的编译模块通过合成default constructor、destructor、assignment copy operator都以
inline方式完成,来避免编译器合出多个default constructor。一个inline函数有静态链接(static
linkage),不会被文件以外者看见。如果函数太复杂,不适合做成inline,就会合成一个
explicit non-inline static实例。
如下例子,编译器为class Bar合成一个default constructor:
#include <iostream> class Foo { public: Foo(){ val = 0; pnext = NULL; } int val; Foo *pnext; }; class Bar : public Foo { public: Foo foo; char *str; }; int main() { Bar bar; // 3ff0 Bar::foo必须在此初始化 if( bar.val || bar.pnext ) { std::cout << "foo参数未初始化为0" << std::endl; } else { std::cout << "foo参数初始化为0" << std::endl; } if( bar.str ) { std::cout << "Bar::str参数未初始化为0" << std::endl; } else { std::cout << "Bar::str参数初始化为0" << std::endl; } }
可以通过编译器生成的汇编代码看到,的确生成了Bar的default constructor,并且调用了
Foo的default constructor,但它并不产生任何初始化Bar::str。初始化Bar::foo是编译器的责任,
而初始化Bar::str则是程序员的责任。
如果class A内含一个或一个以上的member class objects,那么class A的每一个constructor
必须调用每一个member classes的default constructor。编译器会扩张已存在的constructors,在
其中安插一些代码,使得user code被执行之前,先调用必要的default constructors。如下例
子,在class Bar中加入程序员定义的default constructor:
class Bar : public Foo { public: Bar(){ str = NULL; } Foo foo; char *str; };
可以从上面汇编代码看出,的确Bar的default constructor函数被扩张,在开始调用了
Foo::Foo()。
对于有多个class member objects都要求constructor初始化操作,C++语言要求以member
class在class中的声明顺序来调用各个constructors。例如:
可以从汇编代码看到Snow_White的default Constructor的确先调用了Dopey::Dopey(),
Sneezy::Sneezy(), Bashful::Bashful()。
而如果给Snow_White加上default Constructor,如下:
class Snow_White { public: Snow_White(){ mumble = 0; } Dopey dopey; Sneezy sneezy; Bashful bashful; int getValue(){ return mumble; } private: int mumble; };
可以根据汇编代码看到的确Snow_White的default constructor被扩张了。
2、“带有Default Constructor”的Base Class
如果一个没有任何constructors的class派生自一个“带有default constructor”的base class,那么这个derived class的default constructor会被视为nontrivial,并因此需要被合成出来。
#include <iostream> class Dopey { public: Dopey(){ val1 = 0; } int val1; }; class Snow_White : public Dopey { public: int getValue(){ return mumble; } private: int mumble; }; int main() { Snow_White snow_white; if( snow_white.val1 ) { std::cout << "val参数未初始化为0" << std::endl; } else { std::cout << "val参数初始化为0" << std::endl; } if( snow_white.getValue() ) { std::cout << "mumble参数未初始化为0" << std::endl; } else { std::cout << "mumble参数初始化为0" << std::endl; } return 0; }
可以通过汇编代码看到,Snow_White的default constructor被合成出并调用了Dopey的
default constructor。
3、“带有一个Virtual Function”的Class
另外两种情况也要合出default constructor:1)class声明(或继承)一个virtual function。
2)class派生自一个继承串链,其中有一个或更多的virtual base classes。
// Whistle类的虚表 .weak _ZTV7Whistle //若两个或两个以上全局符号(函数或变量名)名字一样, 而其中之一声明为weak symbol(弱符号),则这些全局符号不会引发重定义错误。 链接器会忽略弱符号,去使用普通的全局符号来解析所有对这些符号的引用, 但当普通的全局符号不可用时,链接器会使用弱符号。 .section .rodata._ZTV7Whistle,"aG",@progbits,_ZTV7Whistle,comdat // .section section_name [, "flags"[, %type[,flag_specific_arguments]]] .align 8 .type _ZTV7Whistle, @object .size _ZTV7Whistle, 12 _ZTV7Whistle: .long 0 .long _ZTI7Whistle .long _ZNK7Whistle4flipEv // Bell类的虚表 .weak _ZTV4Bell .section .rodata._ZTV4Bell,"aG",@progbits,_ZTV4Bell,comdat .align 8 .type _ZTV4Bell, @object .size _ZTV4Bell, 12 _ZTV4Bell: .long 0 .long _ZTI4Bell .long _ZNK4Bell4flipEv .weak _ZTV6Widget // Widget类的虚表 .weak _ZTV6Widget .section .rodata._ZTV6Widget,"aG",@progbits,_ZTV6Widget,comdat .align 8 .type _ZTV6Widget, @object .size _ZTV6Widget, 12 _ZTV6Widget: .long 0 .long _ZTI6Widget .long __cxa_pure_virtual // Whistle的typeinfo name .weak _ZTS7Whistle .section .rodata._ZTS7Whistle,"aG",@progbits,_ZTS7Whistle,comdat .type _ZTS7Whistle, @object .size _ZTS7Whistle, 9 _ZTS7Whistle: .string "7Whistle" .weak _ZTI7Whistle // Whistle的typeinfo .weak _ZTI7Whistle .section .rodata._ZTI7Whistle,"aG",@progbits,_ZTI7Whistle,comdat .align 4 .type _ZTI7Whistle, @object .size _ZTI7Whistle, 12 _ZTI7Whistle: .long _ZTVN10__cxxabiv120__si_class_type_infoE+8 .long _ZTS7Whistle .long _ZTI6Widget
下面两个扩展行动在编译器发生:
1)一个virtual function table会被编译出来,内放class的virtual functions地址。
2)在每一个class object中,一个额外的pointer member(也就是vptr)会被编译器合成出来,
内含相关class vtbl的地址。
为了让这个机制发挥功效,编译器必须为每一个Widget(或其派生类的)object的vptr设定
初值,放置适当的virtual table地址。在定义的每一个default constructor和constructor中安插一
些代码。如下:
movl 8(%ebp), %eax movl $_ZTV6Widget+8, (%eax) movl 8(%ebp), %eax movl $_ZTV4Bell+8, (%eax) movl 8(%ebp), %eax movl $_ZTV7Whistle+8, (%eax)
4、“带有一个Virtual Base Class”的Class
#include <iostream> 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; }; void foo( A* pa ) { pa->i = 1024; std::cout << pa->i << std::endl; } int main() { foo( new A ); foo( new C ); return 0; }
0804885a <_ZN1XC1Ev>: 804885a: 55 push %ebp 804885b: 89 e5 mov %esp,%ebp 804885d: 5d pop %ebp 804885e: c3 ret 804885f: 90 nop 08048860 <_ZN1AC2Ev>: 8048860: 55 push %ebp 8048861: 89 e5 mov %esp,%ebp 8048863: 8b 45 0c mov 0xc(%ebp),%eax 8048866: 8b 10 mov (%eax),%edx 8048868: 8b 45 08 mov 0x8(%ebp),%eax 804886b: 89 10 mov %edx,(%eax) 804886d: 5d pop %ebp 804886e: c3 ret 804886f: 90 nop 08048870 <_ZN1AC1Ev>: 8048870: 55 push %ebp 8048871: 89 e5 mov %esp,%ebp 8048873: 83 ec 18 sub $0x18,%esp 8048876: 8b 45 08 mov 0x8(%ebp),%eax 8048879: 83 c0 08 add $0x8,%eax 804887c: 89 04 24 mov %eax,(%esp) 804887f: e8 d6 ff ff ff call 804885a <_ZN1XC1Ev> 8048884: ba fc 89 04 08 mov $0x80489fc,%edx 8048889: 8b 45 08 mov 0x8(%ebp),%eax 804888c: 89 10 mov %edx,(%eax) 804888e: c9 leave 804888f: c3 ret 08048890 <_ZN1BC2Ev>: 8048890: 55 push %ebp 8048891: 89 e5 mov %esp,%ebp 8048893: 8b 45 0c mov 0xc(%ebp),%eax 8048896: 8b 10 mov (%eax),%edx 8048898: 8b 45 08 mov 0x8(%ebp),%eax 804889b: 89 10 mov %edx,(%eax) 804889d: 5d pop %ebp 804889e: c3 ret 804889f: 90 nop 080488a0 <_ZN1CC1Ev>: 80488a0: 55 push %ebp 80488a1: 89 e5 mov %esp,%ebp 80488a3: 83 ec 18 sub $0x18,%esp 80488a6: 8b 45 08 mov 0x8(%ebp),%eax 80488a9: 83 c0 18 add $0x18,%eax 80488ac: 89 04 24 mov %eax,(%esp) 80488af: e8 a6 ff ff ff call 804885a <_ZN1XC1Ev> 80488b4: ba c4 89 04 08 mov $0x80489c4,%edx 80488b9: 8b 45 08 mov 0x8(%ebp),%eax 80488bc: 89 54 24 04 mov %edx,0x4(%esp) 80488c0: 89 04 24 mov %eax,(%esp) 80488c3: e8 98 ff ff ff call 8048860 <_ZN1AC2Ev> 80488c8: b8 c8 89 04 08 mov $0x80489c8,%eax 80488cd: 8b 55 08 mov 0x8(%ebp),%edx 80488d0: 83 c2 08 add $0x8,%edx 80488d3: 89 44 24 04 mov %eax,0x4(%esp) 80488d7: 89 14 24 mov %edx,(%esp) 80488da: e8 b1 ff ff ff call 8048890 <_ZN1BC2Ev> 80488df: ba b4 89 04 08 mov $0x80489b4,%edx 80488e4: 8b 45 08 mov 0x8(%ebp),%eax 80488e7: 89 10 mov %edx,(%eax) 80488e9: ba c0 89 04 08 mov $0x80489c0,%edx 80488ee: 8b 45 08 mov 0x8(%ebp),%eax 80488f1: 89 50 08 mov %edx,0x8(%eax) 80488f4: c9 leave 80488f5: c3 ret 80488f6: 66 90 xchg %ax,%ax 80488f8: 66 90 xchg %ax,%ax 80488fa: 66 90 xchg %ax,%ax 80488fc: 66 90 xchg %ax,%ax 80488fe: 66 90 xchg %ax,%ax
可以看到编译器为A,B,C合成了不同的默认构造函数。
二、Copy Constructor的构造操作
有三种情况,会以一个object的内容作为另一个class object的初值。最明显的一种情况当然就是对一个object做显式的初始化操作:
class X { ... }; X x; // 显式地以一个object的内容作为另一个class object的初值 X xx = x;
另外两种情况是当object被当做参数交给某个函数时:
extern void foo( X x ); void bar() { X xx; // 以xx作为foo()第一个参数的初值(隐式的初始化操作) foo( xx ); }
以及当函数传回一个class object时:
X foo_bar() { X xx; // ... return xx; }
1、Default Memberwise Initialization
当class object以“相同class的另一个object”作为初值,其内部是以所谓的defaultmemberwise initializtion手法完成的,也就是把每一个内建的或派生的data member(例如一个
指针或一个数组)的值,从某个object拷贝一份到另一个object身上。不过它不会拷贝其中的
member class object,而是递归的方式施行memberwise initialization。例如:
#include <iostream> class String { public: // 没有explict cpoy constructor private: char *str; int len; };
一个String object的default memberwise initialization发生在这种情况之下:
String noun( "book" ); String verb = noun; // 其完成方式就好像个别设定每一个members一样 verb.str = noun.str; verb.len = noun.len;
如果一个String object被声明为另一个class的member,像这样:
class Word { public: // ......没有explicit copy constructor private: int _occurs; String _word; // }
那么一个Word object的default memberwise initialization会拷贝其内建的member _occurs,
然后再于String member object _word身上递归实施memberwise initialization。
一个class object可用两种方式复制得到,一种是被初始化,另一种是被指定
(assignment)。从概念上而言,这两个操作分别是以copy constructor和copy assignment
operator完成的。
2、Bitwise Copy Semantics(位逐次拷贝)
在下面程序片段中:#include "Word.h" Word noun("book"); void foo() { Word verb = noun; }
很明显verb是根据noun来初始化。如果class Word的设计者定义了一个copy
constructor,verb的初始化操作会调用它。但如果class没有定义explicit copy constructor,那么
编译器是否会合成一个调用实例,这就得视该class是否展现“bitwise copy semamtics”而定。
// 以下声明展现了bitwise copy semantics class Word { public: Word( const char* ); ~Word( delete[] str ); // ... private: int cnt; char *str; }
这种情况下不需要合成出一个default copy constructor,因为上述声明展现了“default copy
semantics”,而verb的初始化操作也就不需要以一个函数调用收场。
class Word { public: Word( const String& ); ~Word(); // ... private: int cnt; String str; }; // 其中String声明了一个explicit copy constructor class String { public: String( const char* ); String( const String& ); ~String(); // ... };
这种情况下,编译器必须合成一个copy constructor,以便调用member class String object
的copy constructor:
// 一个被合成出来copy constructor // C++伪码 inline Word::Word( const Word& wd ) { str.String::String( wd.str ); cnt = wd.cnt; }
3、不要Bitwise Copy Semantics
有4种情况class不展现出“bitwise copy semantics”1)当class内含一个member object而后者的class声明有一个copy constructor时(不论是被
class设计者显式地声明,就像前面的String那样;或是被编译器合成,像class Word那样)。
2)当class继承自一个base class而后者存在一个copy constructor(也是不论是被显式声明
或是被合成而得)。
3)当class声明了一个或多个virtual functions时。
4)当class派生自一个继承串链,其中有一个或多个virtual base classes时。
下面是后两种情况的讨论。
4、重新设定Virtual Table的指针
#include <iostream> class ZooAnimal { public: ZooAnimal(){ an1 = 0; dr1 = 0; } virtual ~ZooAnimal(){ }; virtual void animate(){ std::cout << "This is ZooAnimal'animate " << an1 << std::endl; } virtual void draw(){ std::cout << "This is ZooAnimal'draw " << dr1 << std::endl; } // ... private: float an1; int dr1; }; class Bear : public ZooAnimal { public: Bear(){ an2 = 0; dr2 = 0; da1 = 0; }; void animate(){ std::cout << "This is Bear'animate " << an2 << std::endl; } void draw(){ std::cout << "This is Bear'draw " << dr2 << std::endl; } virtual void dance(){ std::cout << "This is Bear'dance " << da1 << std::endl; } private: float an2; int dr2; int da1; }; int main() { Bear yogi; Bear winnie = yogi; std::cout << "For yogi: " << std::endl; yogi.animate(); yogi.draw(); std::cout << "For winnie: " << std::endl; winnie.animate(); winnie.draw(); winnie.dance(); }
汇编代码如下:
080488ad <main>: 80488ad: 55 push %ebp 80488ae: 89 e5 mov %esp,%ebp 80488b0: 53 push %ebx 80488b1: 83 e4 f0 and $0xfffffff0,%esp 80488b4: 83 ec 40 sub $0x40,%esp 80488b7: 8d 44 24 10 lea 0x10(%esp),%eax 80488bb: 89 04 24 mov %eax,(%esp) 80488be: e8 2b 02 00 00 call 8048aee <_ZN4BearC1Ev> #调用Bear::Bear() 80488c3: 8d 44 24 10 lea 0x10(%esp),%eax 80488c7: 89 44 24 04 mov %eax,0x4(%esp) 80488cb: 8d 44 24 28 lea 0x28(%esp),%eax 80488cf: 89 04 24 mov %eax,(%esp) 80488d2: e8 45 03 00 00 call 8048c1c <_ZN4BearC1ERKS_> #调用Bear::Bear(Bear const&) 80488d7: c7 44 24 04 c0 8d 04 movl $0x8048dc0,0x4(%esp) 80488de: 08 80488df: c7 04 24 a0 b0 04 08 movl $0x804b0a0,(%esp) 80488e6: e8 65 fe ff ff call 8048750 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 80488eb: c7 44 24 04 80 87 04 movl $0x8048780,0x4(%esp) 80488f2: 08 80488f3: 89 04 24 mov %eax,(%esp) 80488f6: e8 75 fe ff ff call 8048770 <_ZNSolsEPFRSoS_E@plt> 80488fb: 8d 44 24 10 lea 0x10(%esp),%eax 80488ff: 89 04 24 mov %eax,(%esp) 8048902: e8 23 02 00 00 call 8048b2a <_ZN4Bear7animateEv> #调用Bear::animate() 8048907: 8d 44 24 10 lea 0x10(%esp),%eax 804890b: 89 04 24 mov %eax,(%esp) 804890e: e8 5b 02 00 00 call 8048b6e <_ZN4Bear4drawEv> #调用Bear::draw() 8048913: c7 44 24 04 cb 8d 04 movl $0x8048dcb,0x4(%esp) 804891a: 08 804891b: c7 04 24 a0 b0 04 08 movl $0x804b0a0,(%esp) 8048922: e8 29 fe ff ff call 8048750 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> # 调用std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits <char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) 8048927: c7 44 24 04 80 87 04 movl $0x8048780,0x4(%esp) 804892e: 08 804892f: 89 04 24 mov %eax,(%esp) 8048932: e8 39 fe ff ff call 8048770 <_ZNSolsEPFRSoS_E@plt> # 调用std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream <char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&)) 8048937: 8d 44 24 28 lea 0x28(%esp),%eax 804893b: 89 04 24 mov %eax,(%esp) 804893e: e8 e7 01 00 00 call 8048b2a <_ZN4Bear7animateEv> # 调用Bear::animate() 8048943: 8d 44 24 28 lea 0x28(%esp),%eax 8048947: 89 04 24 mov %eax,(%esp) 804894a: e8 1f 02 00 00 call 8048b6e <_ZN4Bear4drawEv> #调用Bear::draw() 804894f: 8d 44 24 28 lea 0x28(%esp),%eax 8048953: 89 04 24 mov %eax,(%esp) 8048956: e8 57 02 00 00 call 8048bb2 <_ZN4Bear5danceEv> # 调用Bear::dance() 804895b: 8d 44 24 28 lea 0x28(%esp),%eax 804895f: 89 04 24 mov %eax,(%esp) 8048962: e8 fd 02 00 00 call 8048c64 <_ZN4BearD1Ev> # 调用Bear::~Bear() 8048967: 8d 44 24 10 lea 0x10(%esp),%eax 804896b: 89 04 24 mov %eax,(%esp) 804896e: e8 f1 02 00 00 call 8048c64 <_ZN4BearD1Ev> # 调用Bear::~Bear() 8048973: b8 00 00 00 00 mov $0x0,%eax 8048978: eb 24 jmp 804899e <main+0xf1> 804897a: 89 c3 mov %eax,%ebx 804897c: 8d 44 24 28 lea 0x28(%esp),%eax 8048980: 89 04 24 mov %eax,(%esp) 8048983: e8 dc 02 00 00 call 8048c64 <_ZN4BearD1Ev> # 调用Bear::~Bear() 8048988: 8d 44 24 10 lea 0x10(%esp),%eax 804898c: 89 04 24 mov %eax,(%esp) 804898f: e8 d0 02 00 00 call 8048c64 <_ZN4BearD1Ev> # 调用Bear::~Bear() 8048994: 89 d8 mov %ebx,%eax 8048996: 89 04 24 mov %eax,(%esp) 8048999: e8 02 fe ff ff call 80487a0 <_Unwind_Resume@plt> 804899e: 8b 5d fc mov -0x4(%ebp),%ebx 80489a1: c9 leave 80489a2: c3 ret # Bear::Bear()默认构造函数 08048aee <_ZN4BearC1Ev>: 8048aee: 55 push %ebp 8048aef: 89 e5 mov %esp,%ebp 8048af1: 83 ec 18 sub $0x18,%esp 8048af4: 8b 45 08 mov 0x8(%ebp),%eax 8048af7: 89 04 24 mov %eax,(%esp) 8048afa: e8 ff fe ff ff call 80489fe <_ZN9ZooAnimalC1Ev> #调用ZooAnimal::ZooAnimal() 8048aff: 8b 45 08 mov 0x8(%ebp),%eax 8048b02: c7 00 e8 8d 04 08 movl $0x8048de8,(%eax) 8048b08: 8b 55 08 mov 0x8(%ebp),%edx 8048b0b: a1 d8 8d 04 08 mov 0x8048dd8,%eax 8048b10: 89 42 0c mov %eax,0xc(%edx) 8048b13: 8b 45 08 mov 0x8(%ebp),%eax 8048b16: c7 40 10 00 00 00 00 movl $0x0,0x10(%eax) 8048b1d: 8b 45 08 mov 0x8(%ebp),%eax 8048b20: c7 40 14 00 00 00 00 movl $0x0,0x14(%eax) 8048b27: c9 leave 8048b28: c3 ret 8048b29: 90 nop #Bear::Bear(Bear const&)默认拷贝构造函数 08048c1c <_ZN4BearC1ERKS_>: 8048c1c: 55 push %ebp 8048c1d: 89 e5 mov %esp,%ebp 8048c1f: 83 ec 18 sub $0x18,%esp 8048c22: 8b 55 0c mov 0xc(%ebp),%edx 8048c25: 8b 45 08 mov 0x8(%ebp),%eax 8048c28: 89 54 24 04 mov %edx,0x4(%esp) 8048c2c: 89 04 24 mov %eax,(%esp) 8048c2f: e8 c2 ff ff ff call 8048bf6 <_ZN9ZooAnimalC1ERKS_> #调用ZooAnimal::ZooAnimal(Bear const&) 8048c34: 8b 45 08 mov 0x8(%ebp),%eax 8048c37: c7 00 e8 8d 04 08 movl $0x8048de8,(%eax) 8048c3d: 8b 45 0c mov 0xc(%ebp),%eax 8048c40: 8b 40 0c mov 0xc(%eax),%eax 8048c43: 8b 55 08 mov 0x8(%ebp),%edx 8048c46: 89 42 0c mov %eax,0xc(%edx) 8048c49: 8b 45 0c mov 0xc(%ebp),%eax 8048c4c: 8b 50 10 mov 0x10(%eax),%edx 8048c4f: 8b 45 08 mov 0x8(%ebp),%eax 8048c52: 89 50 10 mov %edx,0x10(%eax) 8048c55: 8b 45 0c mov 0xc(%ebp),%eax 8048c58: 8b 50 14 mov 0x14(%eax),%edx 8048c5b: 8b 45 08 mov 0x8(%ebp),%eax 8048c5e: 89 50 14 mov %edx,0x14(%eax) 8048c61: c9 leave 8048c62: c3 ret 8048c63: 90 nop
下面是虚表:
# ZooAnimal类的虚表 .section .rodata._ZTV9ZooAnimal,"aG",@progbits,_ZTV9ZooAnimal,comdat .align 8 .type _ZTV9ZooAnimal, @object .size _ZTV9ZooAnimal, 24 _ZTV9ZooAnimal: .long 0 .long _ZTI9ZooAnimal .long _ZN9ZooAnimalD1Ev .long _ZN9ZooAnimalD0Ev .long _ZN9ZooAnimal7animateEv .long _ZN9ZooAnimal4drawEv # Bear类的虚表 .section .rodata._ZTV4Bear,"aG",@progbits,_ZTV4Bear,comdat .align 8 .type _ZTV4Bear, @object .size _ZTV4Bear, 28 _ZTV4Bear: .long 0 .long _ZTI4Bear .long _ZN4BearD1Ev .long _ZN4BearD0Ev .long _ZN4Bear7animateEv .long _ZN4Bear4drawEv .long _ZN4Bear5danceEv
从main函数的调用过程可以看出yogi和winnie调用的Bear虚表所指的Bear::animate()函数和
Bear::draw()函数。
当一个base class object以其derived class的object内容做初始化操作时,其vptr复制操作也
必须保证安全,如下:
int main() { Bear yogi; ZooAnimal franny = yogi; // 这会发生切割行为 std::cout << "For yogi: " << std::endl; yogi.animate(); yogi.draw(); std::cout << "For franny: " << std::endl; franny.animate(); franny.draw(); }
080488ad <main>: 80488ad: 55 push %ebp 80488ae: 89 e5 mov %esp,%ebp 80488b0: 53 push %ebx 80488b1: 83 e4 f0 and $0xfffffff0,%esp 80488b4: 83 ec 40 sub $0x40,%esp 80488b7: 8d 44 24 28 lea 0x28(%esp),%eax 80488bb: 89 04 24 mov %eax,(%esp) 80488be: e8 1f 02 00 00 call 8048ae2 <_ZN4BearC1Ev> # 调用Bear::Bear() 80488c3: 8d 44 24 28 lea 0x28(%esp),%eax 80488c7: 89 44 24 04 mov %eax,0x4(%esp) 80488cb: 8d 44 24 1c lea 0x1c(%esp),%eax 80488cf: 89 04 24 mov %eax,(%esp) 80488d2: e8 13 03 00 00 call 8048bea <_ZN9ZooAnimalC1ERKS_> # 调用ZooAnimal::ZooAnimal(ZooAnimal const&) 80488d7: c7 44 24 04 60 8d 04 movl $0x8048d60,0x4(%esp) 80488de: 08 80488df: c7 04 24 a0 b0 04 08 movl $0x804b0a0,(%esp) 80488e6: e8 65 fe ff ff call 8048750 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 80488eb: c7 44 24 04 80 87 04 movl $0x8048780,0x4(%esp) 80488f2: 08 80488f3: 89 04 24 mov %eax,(%esp) 80488f6: e8 75 fe ff ff call 8048770 <_ZNSolsEPFRSoS_E@plt> 80488fb: 8d 44 24 28 lea 0x28(%esp),%eax 80488ff: 89 04 24 mov %eax,(%esp) 8048902: e8 17 02 00 00 call 8048b1e <_ZN4Bear7animateEv> # 调用Bear::animate() 8048907: 8d 44 24 28 lea 0x28(%esp),%eax 804890b: 89 04 24 mov %eax,(%esp) 804890e: e8 4f 02 00 00 call 8048b62 <_ZN4Bear4drawEv> # 调用Bear::draw() 8048913: c7 44 24 04 6b 8d 04 movl $0x8048d6b,0x4(%esp) 804891a: 08 804891b: c7 04 24 a0 b0 04 08 movl $0x804b0a0,(%esp) 8048922: e8 29 fe ff ff call 8048750 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 8048927: c7 44 24 04 80 87 04 movl $0x8048780,0x4(%esp) 804892e: 08 804892f: 89 04 24 mov %eax,(%esp) 8048932: e8 39 fe ff ff call 8048770 <_ZNSolsEPFRSoS_E@plt> 8048937: 8d 44 24 1c lea 0x1c(%esp),%eax 804893b: 89 04 24 mov %eax,(%esp) 804893e: e8 17 01 00 00 call 8048a5a <_ZN9ZooAnimal7animateEv> # 调用ZooAnimal::animate() 8048943: 8d 44 24 1c lea 0x1c(%esp),%eax 8048947: 89 04 24 mov %eax,(%esp) 804894a: e8 4f 01 00 00 call 8048a9e <_ZN9ZooAnimal4drawEv> # 调用ZooAnimal::draw() 804894f: 8d 44 24 1c lea 0x1c(%esp),%eax 8048953: 89 04 24 mov %eax,(%esp) 8048956: e8 bb 00 00 00 call 8048a16 <_ZN9ZooAnimalD1Ev> # 调用ZooAnimal::~ZooAnimal() 804895b: 8d 44 24 28 lea 0x28(%esp),%eax 804895f: 89 04 24 mov %eax,(%esp) 8048962: e8 a9 02 00 00 call 8048c10 <_ZN4BearD1Ev> # 调用Bear::~Bear() 8048967: b8 00 00 00 00 mov $0x0,%eax 804896c: eb 24 jmp 8048992 <main+0xe5> 804896e: 89 c3 mov %eax,%ebx 8048970: 8d 44 24 1c lea 0x1c(%esp),%eax 8048974: 89 04 24 mov %eax,(%esp) 8048977: e8 9a 00 00 00 call 8048a16 <_ZN9ZooAnimalD1Ev> # 调用ZooAnimal::~ZooAnimal() 804897c: 8d 44 24 28 lea 0x28(%esp),%eax 8048980: 89 04 24 mov %eax,(%esp) 8048983: e8 88 02 00 00 call 8048c10 <_ZN4BearD1Ev> # 调用Bear::~Bear() 8048988: 89 d8 mov %ebx,%eax 804898a: 89 04 24 mov %eax,(%esp) 804898d: e8 0e fe ff ff call 80487a0 <_Unwind_Resume@plt> 8048992: 8b 5d fc mov -0x4(%ebp),%ebx 8048995: c9 leave 8048996: c3 ret
可以看出franny的vptr指向ZooAnimal的virtual table,而非Bear的virtual table(它由yogi的
vptr指出)。
5、处理Virtual Base Class Subobject
一个class object如果以另一个object作为初值,而后者有一个virtual base class subobject,那么也会使“bitwise copy semantics”失效。
#include <iostream> class ZooAnimal { public: ZooAnimal(){ an1 = 0; dr1 = 0; } virtual ~ZooAnimal(){ }; virtual void animate(){ std::cout << "This is ZooAnimal'animate " << std::endl; } virtual void draw(){ std::cout << "This is ZooAnimal'draw " << std::endl; } // ... private: float an1; int dr1; }; class Raccoon : public virtual ZooAnimal { public: Raccoon(){ ra1 = 0; } Raccoon( int val ){ ra1 = val; } void animate(){ std::cout << "This is Raccoon'animate " << std::endl; } void draw(){ std::cout << "This is Raccoon'draw " << std::endl; } private: int ra1; }; class RedPanda : public Raccoon { public: RedPanda(){ re1 = 0; } RedPanda( int val ){ re1 = val; } void animate(){ std::cout << "This is RedPanda'animate " << std::endl; } void draw(){ std::cout << "This is RedPanda'draw " << std::endl; } private: int re1; }; int main() { RedPanda little_red; Raccoon little_critter = little_red; std::cout << "For little_red: " << std::endl; little_red.animate(); little_red.draw(); std::cout << "For little_critter: " << std::endl; little_critter.animate(); little_critter.draw(); }
0804888d <main>: 804888d: 55 push %ebp 804888e: 89 e5 mov %esp,%ebp 8048890: 53 push %ebx 8048891: 83 e4 f0 and $0xfffffff0,%esp 8048894: 83 ec 40 sub $0x40,%esp 8048897: 8d 44 24 28 lea 0x28(%esp),%eax 804889b: 89 04 24 mov %eax,(%esp) 804889e: e8 7b 03 00 00 call 8048c1e <_ZN8RedPandaC1Ev> # 调用RedPanda::RedPanda() 80488a3: 8d 44 24 28 lea 0x28(%esp),%eax 80488a7: 89 44 24 04 mov %eax,0x4(%esp) 80488ab: 8d 44 24 14 lea 0x14(%esp),%eax 80488af: 89 04 24 mov %eax,(%esp) 80488b2: e8 55 04 00 00 call 8048d0c <_ZN7RaccoonC1ERKS_> # 调用Raccoon::Raccoon(Raccoon const&) 80488b7: c7 44 24 04 3b 8f 04 movl $0x8048f3b,0x4(%esp) 80488be: 08 ...... # Raccoon::Raccoon(Raccoon const&) 08048d0c <_ZN7RaccoonC1ERKS_>: 8048d0c: 55 push %ebp 8048d0d: 89 e5 mov %esp,%ebp 8048d0f: 83 ec 18 sub $0x18,%esp 8048d12: 8b 45 0c mov 0xc(%ebp),%eax 8048d15: 8b 00 mov (%eax),%eax 8048d17: 83 e8 0c sub $0xc,%eax 8048d1a: 8b 00 mov (%eax),%eax 8048d1c: 89 c2 mov %eax,%edx 8048d1e: 8b 45 0c mov 0xc(%ebp),%eax 8048d21: 01 c2 add %eax,%edx 8048d23: 8b 45 08 mov 0x8(%ebp),%eax 8048d26: 83 c0 08 add $0x8,%eax 8048d29: 89 54 24 04 mov %edx,0x4(%esp) 8048d2d: 89 04 24 mov %eax,(%esp) 8048d30: e8 b1 ff ff ff call 8048ce6 <_ZN9ZooAnimalC1ERKS_> # 调用ZooAnimal::ZooAnimal(ZooAnimal const&) 8048d35: ba 2c 90 04 08 mov $0x804902c,%edx 8048d3a: 8b 45 08 mov 0x8(%ebp),%eax 8048d3d: 89 10 mov %edx,(%eax) 8048d3f: ba 08 00 00 00 mov $0x8,%edx 8048d44: 8b 45 08 mov 0x8(%ebp),%eax 8048d47: 01 c2 add %eax,%edx 8048d49: b8 50 90 04 08 mov $0x8049050,%eax 8048d4e: 89 02 mov %eax,(%edx) 8048d50: 8b 45 0c mov 0xc(%ebp),%eax 8048d53: 8b 50 04 mov 0x4(%eax),%edx 8048d56: 8b 45 08 mov 0x8(%ebp),%eax 8048d59: 89 50 04 mov %edx,0x4(%eax) 8048d5c: c9 leave 8048d5d: c3 ret
我们看到在4种情况下,class不再保持“bitwise copy semantics”,而且default copy
constructor如果未被声明的话,会被视为nontrivial。如果缺乏一个已声明的copy constructor,
编译器为了正确处理“以一个class object作为另一个class object的初值”,必须合成出一个copy
constructor。
相关文章推荐
- 如何禁止C++默认生成成员函数
- 工厂方法C++实现
- C++中的执行时间测量
- 笔记——《C程序性能优化》[日】片山善夫
- 浅谈C++类(5)--友元
- 浅谈C++类(7)--析构函数
- C++中的rand()、srand()
- 浅谈C++类(1)--概念和构造函数
- c语言 error redefinition of 'xxx' 解决
- 一起talk C栗子吧(第一百七十一回:C语言实例--关闭终端中的回显功能二)
- C++之类和对象的使用(一)
- C语言文件的编译与执行的四个阶段
- c语言学习笔记45
- C语言中怎样判断汉字
- Visual C++开发工具与调试技巧整理
- Effective C++: lambda表达式与闭包.
- 简单的坦克大战模拟小游戏
- c++ 面试整理
- C的结构体和C++结构体
- C语言 求两个数的最大公约数