1.class的内存布局(无虚函数&继承版)
首先,需要弄清楚一件事情,平时所声明的类只是一种类型定义,它本身是没有大小可言的。 我们这里指的类的大小,其实指的是类的对象所占的大小。因此,如果用sizeof运算符对一个类型名操作,得到的是具有该类型实体的大小。
关于类/对象大小的计算
- 首先,类大小的计算遵循结构体的对齐原则
- 类的大小与普通数据成员有关,与成员函数和静态成员无关。即普通成员函数,静态成员函数,静态数据成员,静态常量数据成员均对类的大小无影响,并且类中的静态数据成员是存储在静态存储区,被这个类的所有对象共享
- 虚函数和虚继承对类的大小均有影响
- 空类的大小是一个特殊情况,空类的大小为1字节,它被安插了一个char
class PP
{
public:
PP() = default;
PP(int _a, char _b) :a(_a), b(_b) {};
int name() {
return a;
}
private:
int a;
char b;
int *ptr;
};
我们是在64位下计算的:
可以得出,其实际的需要计算的数据成员只有数据成员a,b,*ptr;我们知道int型的字节为4,char 为1字节,指针8字节,所以总共是13字节,由于字节对齐的原因,所以总共是16字节。
类的虚函数与继承的实现方式
C++中的虚函数的作用主要是实现了多态的机制,这是一种泛型技术,通过指针和引用可以指向不同的具有关系的类对象。
而虚函数是通过虚函数表来实现的,当有一个类有虚函数时,那么就会产生一张虚函数表,此虚函数表在这个类中会产生一个虚指针(vptr),这个指针指向虚函数表,里面存的是虚函数的地址。这样,当有继承时,那么派生类调用基类这个虚函数时,只会存在一份实例,它通过指针指向此函数;like:
class Hogwards
{
public:
Hogwards() = default;
Hogwards(const int _a,const char* _b) :Ha(_a),Hb(_b) {};
virtual string name() {
return Hb;
}
private:
int Ha;
string Hb;
};
class Poter:public Hogwards
{
public:
Poter() = default;
Poter(const int H_a,const char* H_b,const int P_a, const char* P_b) :
Hogwards(H_a,H_b),Pa(P_a),Pb(P_b) {};
string name()
{
return Pb;
}
private:
int Pa;
string Pb;
};
Hogwards的内存布局:
其中_fptr是虚指针,然后指向虚表,其中只有一个虚函数name;
下面是Poter的内存布局:
这里的虚函数就会存在覆盖问题,此时虚指针指向得就是派生类得name,当然这里只有通过指针和引用才能实现。
多重继承以及虚函数问题
class Hogwards
{
public:
Hogwards() = default;
Hogwards(const int _a,const char* _b) :Ba(_a),Bb(_b) {};
virtual string name() {
return Bb;
}
private:
int Ba;
string Bb;
};
class Hermione
{
public:
Hermione() = default;
Hermione(const int _a, const string _b) :a(_a), b(_b) {};
void Avadagendva()
{
cout << "你已经die了" << endl;
}
virtual string name()
{
return b;
}
private:
int a;
string b;
};
class Poter:public Hogwards,public Hermione
{
public:
Poter() = default;
Poter(const int B_a,const char* B_b, const int H_a, const char* H_b,const int P_a, const char* P_b)
:Hogwards(B_a,B_b), Hermione(H_a,H_b),Pa(P_a),Pb(P_b) {};
string name()
{
return Pb;
}
private:
int Pa;
string Pb;
};
这里Poter分别继承了Hogwards和Hermione,由于这两个基类都是有虚函数的,从而就会有虚表:
如果派生类本身也存在独立的虚函数,比如Poter有一个expelliarmus虚函数,那么内部:
是的,这个expelliarmus函数就会在第一个虚指针下放着;
需要注意的是,谁有虚函数,那么就会在前面;
下面是关于虚函数的实现:
#include <iostream>
using namespace std;
class Base1
{
public:
int base1_1;
int base1_2;
virtual void __cdecl base1_fun(int x)
{
std::cout << "Base1::base1_fun(" << x << ")\n";
}
};
class Base2
{
public:
int base2_1;
int base2_2;
virtual void __cdecl base2_fun(int x)
{
std::cout << "Base2::base2_fun(" << x << ")\n";
}
};
class Derive : public Base1, public Base2
{
public:
Derive()
{
base1_1 = 11;
base1_2 = 12;
base2_1 = 21;
base2_2 = 22;
derive1 = 1;
derive2 = 2;
}
int derive1;
int derive2;
virtual void __cdecl derive_fun1() {
cout << "Hello my friend!" << endl;
return ;
};
};
void foo(Base1* pb1, Base2* pb2, Derive* pd)
{
std::cout << "Base1:\n"
<< "\tbase1_1 = " << pb1->base1_1 << "\n"
<< "\tbase1_2 = " << pb1->base1_2 << "\n"
<< std::endl;
std::cout << "Base2:\n"
<< "\tbase2_1 = " << pb2->base2_1 << "\n"
<< "\tbase2_2 = " << pb2->base2_2 << "\n"
<< std::endl;
std::cout << "Derive:\n"
<< "\tderive1 = " << pd->derive1 << "\n"
<< "\tderive2 = " << pd->derive2 << "\n"
<< std::endl;
pb1->base1_fun(11);
pb2->base2_fun(22);
pd->derive_fun1();
}
struct Base1_VPTR_VPTR {
void(__cdecl* base1_fun)(Base1* that, int x);
void(__cdecl *derive_fun1)(Derive *that);
};
struct Base1_VPTR {
Base1_VPTR_VPTR* pvptr;
int base1_1;
int base1_2;
};
struct Base2_VPTR_VPTR {
void(__cdecl* base2_fun)(Base2* that, int x);
};
struct Base2_VPTR {
Base2_VPTR_VPTR* pvptr;
int base2_1;
int base2_2;
};
void __cdecl base1_fun(Base1* that, int x)
{
std::cout << "Base1::base1_fun(" << x << ")\n";
}
void __cdecl base2_fun(Base2* that, int x)
{
std::cout << "Base2::base2_fun(" << x << ")\n";
}
//这里没有用到,因为Derive 的虚函数会放在第一个虚指针所指的虚表里面
//所以Base的虚表需要重新构造,这就是为什么我上面的Base1的虚表有两个函数
struct Derive_C_VPTR
{
void(__cdecl *derive_fun1)(Derive *that);
};
struct Derive_C
{
Base1_VPTR base1;
Base2_VPTR base2;
int derive1;
int derive2;
};
void __cdecl derive_fun1(Derive* that)
{
cout << "Hello my friend!" << endl;
return;
}
int main()
{
Derive dd;
foo(&dd, &dd, &dd);
dd.derive_fun1();
Derive_C d;
Base1_VPTR_VPTR base1_vptr_vptr;
Base2_VPTR_VPTR base2_vptr_vptr;
Derive_C_VPTR C_vptr;
base1_vptr_vptr.base1_fun = base1_fun;
base1_vptr_vptr.derive_fun1 = derive_fun1;
base2_vptr_vptr.base2_fun = base2_fun;
C_vptr.derive_fun1 = derive_fun1;
d.base1.base1_1 = 11;
d.base1.base1_2 = 12;
d.base1.pvptr = &base1_vptr_vptr;
d.base2.base2_1 = 21;
d.base2.base2_2 = 22;
d.base2.pvptr = &base2_vptr_vptr;
d.derive1 = 1;
d.derive2 = 2;
foo((Base1*)&d.base1, (Base2*)&d.base2, (Derive*)&d);
return 0;
}