错误认知
没有任何构造函数的时候, 编译器总会生成默认构造函数
编译器仅在必要的时候生成默认构造函数
析构函数同理
条件 :
- 有带有默认构造函数的member
- 有带有默认构造函数的base class
- 有virtual function
- 有virtual inherit
任何对象都带有vptr / 可以对任何对象指针进行 dynamic_cast 操作
只有对象具有 多态 属性的时候 , 才具有 vptr , 才可以对其指针进行 dynamic_cast .
对一个没有多态 属性的指针进行dynamic_cast会导致编译器报错.
class GrandPa
{
public:
int a;
char a1;
// virtual void func(){}
};
class Father : public GrandPa
{
public:
int b;
char a2;
};
class Son : public Father
{
public:
int i;
char a3;
};
int main()
{
Son s;
Father f ;
GrandPa * p1 = &s;
GrandPa * p2 = &f;
printf("The addr of p1 : %p\n" , dynamic_cast<Son*>(p1) ) ;
printf("The addr of p1 : %p\n" , dynamic_cast<Father*>(p1) ) ;
printf("The addr of p2 : %p\n" , dynamic_cast<Son*>(p2) ) ;
return 0;
}
报错:
c++_test.cpp: In function ‘int main()’:
c++_test.cpp:42:63: error: cannot dynamic_cast ‘p1’ (of type ‘class GrandPa*’) to type ‘class Son*’ (source type is not polymorphic)
c++_test.cpp:43:66: error: cannot dynamic_cast ‘p1’ (of type ‘class GrandPa*’) to type ‘class Father*’ (source type is not polymorphic)
c++_test.cpp:44:63: error: cannot dynamic_cast ‘p2’ (of type ‘class GrandPa*’) to type ‘class Son*’ (source type is not polymorphic)
取消对GrandPa的虚函数的屏蔽 , 结果:
The addr of p1 : 0x7fff8f017ee0
The addr of p1 : 0x7fff8f017ee0
The addr of p2 : (nil)
新知识点
C++ 语言保证”出现在派生类中的基类对象 有其原样完整性”
指向Data Member 的指针含义 : member 在对象中的偏移
例子 :
class GrandPa
{
public:
int a;
char a1;
};
class Father : public GrandPa
{
public:
char a2;
};
class Son : public Father
{
public:
char a3;
};
#include <iostream>
#include <stdio.h>
using std::cout;
using std::endl;
int main()
{
cout<<"Sizeof GrandPa : "<<sizeof(GrandPa)<<endl;
cout<<"Sizeof Father : "<<sizeof(Father)<<endl;
cout<<"Sizeof Son : "<<sizeof(Son)<<endl;
printf("The offset of a1 : %p\n", &Son::a1 );
printf("The offset of a2 : %p\n", &Son::a2 );
printf("The offset of a3 : %p\n", &Son::a3 );
return 0;
}
输出结果:
Linux / g++
命令
g++ test.cpp
输出
Sizeof GrandPa : 8
Sizeof Father : 12
Sizeof Son : 12
The offset of a1 : 0x4
The offset of a2 : 0x8
The offset of a3 : 0x9
Window / Visual Studio
Sizeof GrandPa : 8
Sizeof Father : 12
Sizeof Son : 16
The offset of a1 : 0x4
The offset of a2 : 0x8
The offset of a3 : 0xC
分析 :
- 由于地址对齐,GranPa的大小是8byte 而不是5byte
- a1 的偏移为4
- GranPa 类的大小为8byte , 派生类Father的member只能从第8地址(第九位)开始排。
- a2 的偏移为8 , 这显然是为了保持GrandPa类的原样完整性实现的
- 对于VS Son的大小16byte和a3 的偏移12显然也是保持Father类原样完整性 。
问题:
为什么Linux / GCC 下的 Son的大小也是12 ? 为什么 a3 的偏移是9而不是12
将代码改为:
- 在Son类中添加了一个int , 声明先于a3 .
class GrandPa
{
public:
int a;
char a1;
};
class Father : public GrandPa
{
public:
//int b;
char a2;
};
class Son : public Father
{
public:
int i;
char a3;
};
#include <iostream>
#include <stdio.h>
using std::cout;
using std::endl;
int main()
{
cout<<"Size of GrandPa : "<<sizeof(GrandPa)<<endl;
cout<<"Size of Father : "<<sizeof(Father)<<endl;
cout<<"Size of Son : "<<sizeof(Son)<<endl;
printf("The offset of a : %p\n", &Son::a);
printf("The offset of a1 : %p\n", &Son::a1);
printf("The offset of a2 : %p\n", &Son::a2);
printf("The offset of a3 : %p\n", &Son::a3);
printf("The offset of i : %p\n", &Son::i);
return 0;
}
输出:
Size of GrandPa : 8
Size of Father : 12
Size of Son : 20
The offset of a : (nil)
The offset of a1 : 0x4
The offset of a2 : 0x8
The offset of a3 : 0x10
The offset of i : 0xc
可以看到 g++ 对内存做了适当的优化 , 将a3 先于 i 存放.
- 在Father 类中添加int
对上面的代码中的
//int b
打开 , 得到输出:
Size of GrandPa : 8
Size of Father : 16
Size of Son : 24
The offset of a : (nil)
The offset of a1 : 0x4
The offset of a2 : 0xc
The offset of a3 : 0x14
The offset of i : 0x10
这一次没有之前的优化了 . 可以推断 :
g++ 在保证父类的原样完整性的时候, 如果父类也是个派生类, 那么将对父类仅保证 分段 的原样完整性,
也就是说 , 在第一个代码例子中, Son类中Father类的原样完整性是通过保证GrandPa类的原样完整行和Father类中独有的数据
char a2
的原样完整性来进行的 . 这样虽然Father类的大小是12 byte, 但是原样完整性仅仅需要9byte !!!