- Default Constructor什么时候被编译器合成?
- Copy Constructor什么时候被编译器合成?
- 返回值优化操作
- 初始化列表什么时候使用能提高程序效率?
- 内存布局情况
- 单继承
- 单继承+虚函数
- 增加虚函数后,内存布局
- 多继承
- 增加虚函数后,内存布局
- 虚继承(菱形继承)
- 增加虚函数后,内存布局
默认构造函数只有在编译器需要的时候在会被合成出来,什么是编译器需要的时候,见下4种条件(nontrivial default constructor):
- 带有 Default Constructor 的 Member Class Object
如果该类有构造函数,则扩张member class 中的默认构造函数 - 带有 Default Constructor 的 base Class
- 带有一个 Virtual Function 的 Class
编译器执行两个扩张行动:- 一个virtual function table被编译器产生出来;内放class的 virtual function
- 在每一个class object中,一个额外的vptr被编译器合成,内含相关class-vtbl的地址
- 带有一个 Virtual base Class 的 Class
我以前对默认构造函数的误解:
1.任何class如果没有定义默认构造函数,就会被合成一个出来
2.编译器合成的默认构造函数会显示设定每一个class data的默认值。
当一个类没有Bitwise Copy Semantics时,编译器会合成对应的拷贝构造函数,因为默认拷贝是逐位拷贝,当逐位拷贝语义失效时,编译器需要为该类合成一个拷贝构造函数。
有以下几种情况需要合成:
- 当类中有一个类成员,而该类成员声明有一个拷贝构造函数
- 当类继承子一个base class,而该base class存在一个拷贝构造函数
- 当class声明了一个或者多个virtual funcitons时
- 当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]为虚表



