栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

深度探索C++对象模型笔记

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

深度探索C++对象模型笔记

深度探索C++对象模型
  • Default Constructor什么时候被编译器合成?
  • Copy Constructor什么时候被编译器合成?
  • 返回值优化操作
  • 初始化列表什么时候使用能提高程序效率?
  • 内存布局情况
    • 单继承
    • 单继承+虚函数
      • 增加虚函数后,内存布局
    • 多继承
      • 增加虚函数后,内存布局
    • 虚继承(菱形继承)
      • 增加虚函数后,内存布局

Default Constructor什么时候被编译器合成?

默认构造函数只有在编译器需要的时候在会被合成出来,什么是编译器需要的时候,见下4种条件(nontrivial default constructor):

  1. 带有 Default Constructor 的 Member Class Object
    如果该类有构造函数,则扩张member class 中的默认构造函数
  2. 带有 Default Constructor 的 base Class
  3. 带有一个 Virtual Function 的 Class
    编译器执行两个扩张行动:
    1. 一个virtual function table被编译器产生出来;内放class的 virtual function
    2. 在每一个class object中,一个额外的vptr被编译器合成,内含相关class-vtbl的地址
  4. 带有一个 Virtual base Class 的 Class

我以前对默认构造函数的误解:
1.任何class如果没有定义默认构造函数,就会被合成一个出来
2.编译器合成的默认构造函数会显示设定每一个class data的默认值。

Copy Constructor什么时候被编译器合成?

当一个类没有Bitwise Copy Semantics时,编译器会合成对应的拷贝构造函数,因为默认拷贝是逐位拷贝,当逐位拷贝语义失效时,编译器需要为该类合成一个拷贝构造函数。
有以下几种情况需要合成:

  1. 当类中有一个类成员,而该类成员声明有一个拷贝构造函数
  2. 当类继承子一个base class,而该base class存在一个拷贝构造函数
  3. 当class声明了一个或者多个virtual funcitons时
  4. 当class派生自一个继承链,其中有一个或者多个virtual base class时

情况1,2,编译器必须将member或base class中的copy constructors调用操作安插到被合成的copy constructor中

考虑情况3:

class ZoomAnimal{
	virtual void draw(){...}
};
class Bear:public ZoomAnimal{
	virtual void draw(){...}
};

若执行以下操作

Bear yogi;
Bear winnie = yogi;

则完成bitwise copy的拷贝操作时,winnie的vptr会复制yogi的vptr,yogi的vptr指向Bear的virtual table,这么做没什么问题。
然而,当发生如下复制操作时

ZooAnimal franny = yogi;

这时操作便会出现问题,此时再执行bitwise copy操作时,franny的vptr复制yogi的的vptr, 指向Bear的table,显然不对。此时便需要编译器接管,初始化合适的vptr。

返回值优化操作 初始化列表什么时候使用能提高程序效率? 内存布局情况 单继承

考虑如下代码

class Point2d{
protected:
	float _x,_y;
};
class Point3d:public Point2d{
protected:
	float _z;
};

其内存布局为

考虑如下情况:

因为要确保赋值的正确性,如Concrete1 赋值给 Concrete2,如上代码的内存布局如下:

sizeof(Concrete1) = 8
sizeof(Concrete2) = 12
sizeof(Concrete3) = 16

单继承+虚函数

若如上的Point2d类中加上virtual后,内存布局如下:

增加虚函数后,内存布局

考虑如下代码

class Point{
public:
	virtual ~Point2d();
	virtual Point& mult(float)=0;
	virtual float y() const{return 0;}
	virtual float z() const{return 0;}
protected:
	float _x;
};
class Point2d:public Point{
public:
	...
	~Point2d();
	 Point& mult(float);
	 float y() const {return _y};
protected:
	float _y;
};
class Point3d:public Point2d{
public:
	~Point3d();
	Point3d& mult(float);
	float z() const {return _z};
protected:
	float _z;
};

其内存布局为:

这里我自己的理解:每个类共享一份虚表,而每个单继承虚函数的实例,都会由编译器初始化其vptr和成员变量,其中vptr指向公共的虚表。

多继承

考虑如下多继承

其内存布局为:

这里注意,若执行

Vertex3d v3d;
Vertex* pv;
pv = &v3d;

则编译器内部需要执行这样的转化:将pv的指针指向Vertex类内存的位置
计算this指针的方法为
pv = (Vertex*)((char*)&v3d + sizeof(Point3d));

增加虚函数后,内存布局
class base1{
public:
	base1();
	~base1();
	virtual void SpeakClearly();
	virtual base1* clone() const;
protected:
	float data_base1;
};
class base2{
public:
	base2();
	~base2();
	virtual void mumble();
	virtual base2* clone() const;
protected:
	float data_base2;
};
class Derived:public base1,public base2{
	Derived();
	virtual ~Derived();
	virtual Derived* clone() const;
protected:
	float data_Derived;
};

虚继承(菱形继承)

理解虚继承,虚继承的含义为:当某个类被虚继承,则表示它会被共享,即无论被继承多次,在内存中只会有一份存在。
考虑如下虚继承

class Point2d{
public:
	...
protected:
	float _x,_y;
};

class Vertex:public Point2d{
public:
	...
protected:
	Vertex*	next;
};

class Point3d:public Point2d{
public:
	...
protected:
	float _z;
};

class Vertex3d:public vertex,public Point3d{
public:
	...
protected:
	float mumble;
}

其内存布局:

虚基类实现细节:编译器会在继承虚基类的Derived class中安插一个指针(此处为Point2d*),该指针指向虚基类,来实现共享操作。

增加虚函数后,内存布局


内存布局:

注意这里_vptr_Point3d指向的表,table[-1]的位置指向虚基类,table[0-n]为虚表

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/316918.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号