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

C++对象模型学习——构造函数语意学

2016-07-04 00:00 525 查看
摘要: 通过《深入探索C++对象模型》第二章学习C++

一、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,而后者有default

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:

#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”作为初值,其内部是以所谓的default

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