首页
社区
课程
招聘
[脚印]2011年25周6.25 C++默认构造和拷贝构造
2011-6-25 12:12 4531

[脚印]2011年25周6.25 C++默认构造和拷贝构造

2011-6-25 12:12
4531
声明: 菜菜的成长总免不了需要施肥,灌溉,除虫等照料,希望能多一点阳光,

这里要感谢下
     evikis, 邓韬和pencil的批评和鼓励。

内容都是书籍上的内容,对内容按个人习惯进行整理,以方便后续查阅之用,如果不妥之处还望指教。
阅读书籍信息:
<<高质量C/C++>> 第三版 第13章对象的初始化/拷贝和析构 电子工业出版社
<<深度探索C++对象模型>> 第2章构造函数语义学 华中科技大学出版社

这里主要整理了什么情况下编译器会自动生成默认的构造和拷贝构造的情况,以及初始化列表的顺序。一般情况都会写构造来初始化成员变量,但曾经被这里问题困扰,只是绕过(自己写),却没有很好的了解实现的机制。
编译器生成默认构造和默认拷贝构造的情况如下:
默认构造:
本身没有默认构造,但是却包含有带默认构造的对象(继承的基类有,成员对象有)
                              包含虚函数需要处理初始化虚表操作(自身的,继承的,成员变量的)和虚继承
默认拷贝构造:
本身没有默认拷贝构造,但是却包含有带默认拷贝构造的对象(继承的基类有,成员对象有)
                     包含虚函数需要处理初始化虚表操作(自身的,继承的,成员变量的)和虚继承

一:一般不存在默认构造和拷贝构造的情况
        如果类可以用memcpy来完成默认构造或拷贝构造,也不会对使用操作影响的(类中没有下面要说的情况),就不需要有构造的过程。
class CAnimal
{
public:
    bool isCanSay(){return false;}
    static bool isCanFly(){return false;}

public:
    char m_szName[16];
    static int m_snType;
};

CAnimal TestDefalutConstruct(CAnimal AnimalObj)
{
    CAnimal varObj = AnimalObj;
    
    return varObj;
}

int _tmain(int argc, _TCHAR* argv[])
{    
    CAnimal AnimalObj;
    CAnimal AnimalTest(AnimalObj);
    TestDefalutConstruct(AnimalTest);
}

   CAnimal AnimalObj;
    TestDefalutConstruct(AnimalObj); 
00182495  sub         esp,10h 			//临时对象局部空间(只有一个数组10h)
00182498  mov         eax,esp 
0018249A  mov         ecx,dword ptr [ebp-18h]   //AnimalObj的this
0018249D  mov         dword ptr [eax],ecx 
0018249F  mov         edx,dword ptr [ebp-14h] 
001824A2  mov         dword ptr [eax+4],edx 
001824A5  mov         ecx,dword ptr [ebp-10h] 
001824A8  mov         dword ptr [eax+8],ecx 
001824AB  mov         edx,dword ptr [ebp-0Ch] 
001824AE  mov         dword ptr [eax+0Ch],edx    //按位copy
001824B1  lea         eax,[ebp-0FCh] 
001824B7  push        eax  						//数据块使用引用(指针)的方式操作进行优化
001824B8  call        TestDefalutConstruct (1811E5h) 



CAnimal TestDefalutConstruct(CAnimal AnimalObj)
{
    CAnimal varObj = AnimalObj;
00182378  mov         eax,dword ptr [ebp+0Ch] 
0018237B  mov         dword ptr [ebp-18h],eax 
0018237E  mov         ecx,dword ptr [ebp+10h] 
00182381  mov         dword ptr [ebp-14h],ecx 
00182384  mov         edx,dword ptr [ebp+14h] 		//按位copy
00182387  mov         dword ptr [ebp-10h],edx 
0018238A  mov         eax,dword ptr [ebp+18h] 
0018238D  mov         dword ptr [ebp-0Ch],eax 
    return varObj;
001823A8  mov         eax,dword ptr [ebp+8] 		//返回结构体使用引用(指针的方式)
001823AB  mov         ecx,dword ptr [ebp-18h] 	
001823AE  mov         dword ptr [eax],ecx 
001823B0  mov         edx,dword ptr [ebp-14h] 	//保存了返回对象的值
001823B3  mov         dword ptr [eax+4],edx 
001823B6  mov         ecx,dword ptr [ebp-10h] 
001823B9  mov         dword ptr [eax+8],ecx 
001823BC  mov         edx,dword ptr [ebp-0Ch] 
001823BF  mov         dword ptr [eax+0Ch],edx 
001823C2  mov         eax,dword ptr [ebp+8] 
}
//call        TestDefalutConstruct (1811E5h)返回之后
001824BD  add         esp,14h 
001824C0  mov         ecx,dword ptr [eax] 	//这里就是返回值,这个eax就是所谓的引用方式
001824C2  mov         dword ptr [ebp-114h],ecx 
001824C8  mov         edx,dword ptr [eax+4] 
001824CB  mov         dword ptr [ebp-110h],edx 
001824D1  mov         ecx,dword ptr [eax+8] 
001824D4  mov         dword ptr [ebp-10Ch],ecx 
001824DA  mov         edx,dword ptr [eax+0Ch] 
001824DD  mov         dword ptr [ebp-108h],edx
对于类似结构体做返回值的操作我看的2种情况:
1)这里的有优化的情况,通过传递引用(指针)来完成值的传出;
2)没有优化的情况返回函数的中结构体的首址,外部再做一次copy赋值的操作
实例的内存结构中只存在成员变量,没有需要编译器特别处理的地方,这里就可以把class当成C语言的struct来看待,如果类中只有简单的基本数据类型,也可以拆开看待。
这里成员只有一个基本数据类型int,对类的成员操作可以简单认为为只对这个int变量操作,而类中的成员函数也可以看成是一般函数即可(这里不是全局的,还是存在作用域的)。
013823DB  mov         eax,dword ptr [AnimalObj] 
013823DE  push        eax  							//只有一个int成员
013823DF  call        TestDefalutConstruct (13811E5h)


二:编译器生成默认构造的情况
编译器生成是在其需要的情况下才会有的动作,这个需要只要是:有需要处理构造过程和初始化虚表的操作的情况下。
1)类自身没有默认构造,但有需要调用的默认构造函数
        有构造函数时的构造过程为:先基类,再成员,最后自己。自己没写构造函数怎么会完成对基类和成员对象构造的调用呢?其实就是编译器插入的调用的代码完成对基类和成员对象的构造函数的调用,这个插入动作就是编译器需要处理的。
示例代码
class CAnimal
{
public:
    CAnimal()
    { 
        strncpy(m_szName, "Test!", sizeof(m_szName));
    }
    bool isCanSay(){return false;}
    static bool isCanFly(){return false;}

public:
    char m_szName[16];
    static int m_snType;
};

int CAnimal::m_snType = 0x12345678;

class CCat :  public CAnimal
{
public:
    int  m_nCatTest;
    int  m_nTest;
};

    CCat MyCat;
00262348  lea         ecx,[ebp-20h] 
0026234B  call        CAnimal::isCanSay (2612C1h)
跳表地址:002612C1  jmp         CCat::CCat (261630h)
CCat::CCat:
00261650  mov         dword ptr [ebp-8],ecx 
00261653  mov         ecx,dword ptr [this] 
00261656  call        CAnimal::CAnimal (2612BCh)
这里需要调用基类的构造,编译器插足,生成了默认构造完成对基类的调用,成员对象有默认构造的情况雷同。

2)类中有虚表(不论虚表是自己的,继承的,还是成员对象的)
        因为虚表需要处理化,这个活我们没有做,编译器此时又不得不插足来完成初始化的过程,也就要生成默认的构造函数来完成初始化操作。
实例代码只是增加了CAnimal的虚析构
    virtual ~CAnimal()
    {
      _asm NOP 
    }

    CAnimal AnimalObj;
000D499F  lea         ecx,[ebp-4Ch] 
000D49A2  call        CAnimal::CAnimal (0D12BCh) 
Debug版中跳表000D12BC  jmp         CAnimal::CAnimal (0D23D0h)
CAnimal::CAnimal:
000D23F0  mov         dword ptr [ebp-8],ecx 
000D23F3  mov         eax,dword ptr [this] 
000D23F6  mov         dword ptr [eax],offset CAnimal::`vftable' (0D7840h)   //初始化虚表
000D23FC  mov         eax,dword ptr [this] 
000D23FF  pop         edi  

三 编译器生成默认拷贝构造的情况
        编译器生成默认拷贝构造涉及的情况和二基本相同,拷贝构造发生的情况如下:
Class X; class Y=X; 相同类型直接赋值,或派生类赋值给基类(有截取)
将对象作为参数或返回一个对象都会存在拷贝构造
1)相同的类型
    CAnimal AnimalObj;
010E23B0  lea         ecx,[ebp-40h] 
010E23B3  call        CAnimal::CAnimal (10E10D7h) 
    CAnimal TestObj = AnimalObj;
010E23B8  lea         eax,[ebp-40h] 
010E23BB  push        eax  
010E23BC  lea         ecx,[ebp-5Ch] 
010E23BF  call        CAnimal::CAnimal (10E10FAh)

CAnimal::CAnimal:
010E25A0  mov         dword ptr [ebp-8],ecx 
010E25A3  mov         eax,dword ptr [__that]   类中数据成员copy值
010E25A6  mov         ecx,dword ptr [this] 
。。。。。。
010E25BC  mov         dword ptr [ecx+0Ch],eax 
010E25BF  mov         eax,dword ptr [__that] 
010E25C2  add         eax,10h 
010E25C5  push        eax  
010E25C6  mov         ecx,dword ptr [this] 
010E25C9  add         ecx,10h 
010E25CC  call        CEat::CEat (10E11EAh)	调用成员对象的拷贝构造
跳表010E11EA  jmp         CEat::CEat (10E2610h)
  CEat(const CEat& obj)
010E2610  push        ebp  
2)派生类赋值给基类,
        基类只截取了派生类中包含的部分,类似 char = ((char*)int)[0]的操作
    CAnimal AnimalObj = MyCat;
013323A6  lea         eax,[MyCat] 
013323A9  push        eax  
013323AA  lea         ecx,[AnimalObj] 
013323AD  call        CAnimal::CAnimal (13310FAh) 

01332120  mov         dword ptr [ebp-8],ecx 
01332123  mov         eax,dword ptr [__that] 
01332126  push        eax  
01332127  mov         ecx,dword ptr [this] 
0133212A  call        CEat::CEat (13311EAh) 
0133212F  mov         eax,dword ptr [this]

    CCat TestObj = MyCat;
013323B2  lea         eax,[MyCat] 
013323B5  push        eax  
013323B6  lea         ecx,[TestObj] 
013323B9  call        CCat::CCat (1331294h)

013325A0  mov         dword ptr [ebp-8],ecx 
013325A3  mov         eax,dword ptr [__that] 
013325A6  push        eax  
013325A7  mov         ecx,dword ptr [this] 
013325AA  call        CAnimal::CAnimal (13310FAh) 
013325AF  mov         eax,dword ptr [this] 
013325B2  mov         ecx,dword ptr [__that] 
013325B5  mov         edx,dword ptr [ecx+4] 
013325B8  mov         dword ptr [eax+4],edx 
013325BB  mov         eax,dword ptr [this] 
013325BE  mov         ecx,dword ptr [__that] 
013325C1  mov         edx,dword ptr [ecx+8] 
013325C4  mov         dword ptr [eax+8],edx 
013325C7  mov         eax,dword ptr [this]

四 默认构造和拷贝构造,在虚继承情况下编译器也会插足。
虚继承和非虚继承有何变化
	class CAnimal
{
public:
	int m_nType;
};

class CCat :  public CAnimal
{
public:
    int  m_nCatTest;
    int  m_nTest;
};
CCat MyCat;
没有任何汇编指令,其实就是当局部变量了。

class CCat :  public virtual CAnimal
    CCat MyCat;
00E4239E  push        1    
00E423A0  lea         ecx,[MyCat] 
00E423A3  call        CEat::CEat (0E412A8h)

CCat::CCat:
00E41680  mov         dword ptr [ebp-8],ecx 
00E41683  cmp         dword ptr [ebp+8],0 
00E41687  je          CCat::CCat+32h (0E41692h) 
00E41689  mov         eax,dword ptr [this] 
00E4168C  mov         dword ptr [eax],offset CCat::`vbtable' (0E4783Ch) //原来增加了虚表,这个返回到上面的情况了
00E41692  mov         eax,dword ptr [this]
 
因为虚继承需要初始化虚表,所有这种情况又回到了上面有虚表的情况,编译器会增加默认构造,需要时也会有默认拷贝构造。
五 初始化列表
使用参数列表初始化的情况
1)        初始化引用
2)        初始化const成员
3)        调用基类或成员对象的非默认构造(代码参数的)
class CAnimal
{
public:
    CAnimal()
    { 
        strncpy(m_szName, "Test!", sizeof(m_szName));
    }
    virtual ~CAnimal()
    {
       _asm NOP 
    }
    virtual bool isCanSay(){return false;}

public:
    char m_szName[16];
    static int m_snType;
};

int CAnimal::m_snType = 0x12345678;

class CCat :  public CAnimal
{
public:
    CCat():m_nCatTest(0x123)
    {
        m_snType = 0x456;
        memset(m_szName, 0, sizeof(m_szName));
    }
    virtual bool isEatFish(){return true;}
    int  m_nCatTest;
};
    CCat MyCat;
01264990  lea         ecx,[ebp-2Ch] 
01264993  call        CCat::CCat (12612B7h) 
01264998  mov         dword ptr [ebp-4],0

    CCat():m_nCatTest(0x123)
    {
01263600  mov         dword ptr [ebp-8],ecx 
01263603  mov         ecx,dword ptr [this] 
01263606  call        CAnimal::CAnimal (1261280h)  //调用基类的构造
0126360B  mov         eax,dword ptr [this] 
0126360E  mov         dword ptr [eax],offset CCat::`vftable' (1267B40h) //保存虚表
01263614  mov         eax,dword ptr [this] 
01263617  mov         dword ptr [eax+14h],123h //初始化列表
        m_snType = 0x456;
0126361E  mov         dword ptr [CAnimal::m_snType (126A00Ch)],456h //构造函数内
        memset(m_szName, 0, sizeof(m_szName));
01263628  push        10h  
0126362A  push        0    
0126362C  mov         eax,dword ptr [this] 
0126362F  add         eax,4 
01263632  push        eax  
01263633  call        @ILT+170(_memset) (12610AFh) 
01263638  add         esp,0Ch
处理类表的顺序不是按列表的写的前后顺序而是按照声明的先后顺序,这里看下
class CCat :  public CAnimal
{
public:
    CCat():m_nTest(0x123),m_nCatTest(m_nTest)
    {
        m_snType = 0x456;
        memset(m_szName, 0, sizeof(m_szName));
    }
    virtual bool isEatFish(){return true;}
    int  m_nCatTest;
    int  m_nTest;
};
00BA3614  mov         eax,dword ptr [this] 
00BA3617  mov         ecx,dword ptr [this] 
00BA361A  mov         edx,dword ptr [ecx+18h] 
00BA361D  mov         dword ptr [eax+14h],edx 		//m_nCateTest
00BA3620  mov         eax,dword ptr [this] 
00BA3623  mov         dword ptr [eax+18h],123h 	//m_nTest
        m_snType = 0x456;
看下监视窗口中的值(图一)



有继承关系的构造顺序是先构造继承的最顶端(也就是祖先),也是排资论辈的,析构是相反顺序,初始化列表是按顺明顺序,基类的会更早被初始化。
另外在调用父类构造时如果没有参数则无参构造,否则就要调用带参构造
在实际情况中最好使用显示的构造函数,如果确实不需要,为了避免一些隐患(浅拷贝)可以将构造函数私有.

                                 五边形(2011年6月25日-26日 双休日)

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (6)
雪    币: 415
活跃值: (34)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
笨奔 1 2011-6-25 13:48
2
0
学海无涯,回头是岸。
雪    币: 596
活跃值: (449)
能力值: ( LV12,RANK:320 )
在线值:
发帖
回帖
粉丝
evilkis 7 2011-6-25 14:00
3
0
说句不好听的,感觉你在误人子弟,你只是在读汇编代码,根本不懂机制.上面说的有些是错误的,有些是正确的,但是你却不知道为何这样....例如对象传参且对象中没有构造函数,如果对象非静态成员数量很少,很简单,则会一个一个把成员值push传参,如果较大则会申请一段栈空间浅拷贝把对象的非静态成员值,拷贝到申请的空间里.然后传递这个临时对像的地址就可以.又比如,如果对象中存在虚表,则对象传参,不管类中有无构造函数,在创建临时对象时都会调用构造函数(没有构造函数编译器此时会提供默认构造函数),原因很简单,因为虚表必须要在构造函数中初始化....其他的就不说了:说实在的,这东西自己当笔记就行了,发出来就不好了...免得让错误害人...
雪    币: 276
活跃值: (709)
能力值: ( LV15,RANK:520 )
在线值:
发帖
回帖
粉丝
邓韬 9 2011-6-25 20:48
4
0
我也想说:误认子弟,大家还是自己看书好,看不懂就MSDN一下。
雪    币: 1163
活跃值: (137)
能力值: ( LV12,RANK:230 )
在线值:
发帖
回帖
粉丝
pencil 5 2011-6-25 22:43
5
0
大体理解文章的意思,但通篇缺乏条理,建议你整理下再发。
雪    币: 167
活跃值: (1534)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
Nisy 5 2011-6-26 15:21
6
0
我正做一套C++的视频 有兴趣可以关注一下

我没看过几本书 对C++的理解可能也不全面 视频主要是从逻辑层和底层来展开 有兴趣可以关注一下

http://www.sicaril.com/thread-738-1-1.html  看完前三课了 可以Q我 如果前三课OK 或许会送你一套KEY
雪    币: 185
活跃值: (130)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
五边形 1 2011-6-26 15:21
7
0
感谢LS的几位诚恳的意见和建议,我这里检讨下,内容稍作整理,不妥之处还望指点。
游客
登录 | 注册 方可回帖
返回