对于C++OBP的理解

对于C++OBP的理解

前言:

因为自己查阅文章时经常遇到,这些问题:因为环境不同而导致结果不一致、因为文章跳跃度太大而导致无法理解、因为文章代码运行结果不同而苦恼。

所以为了看我文章的人不会遭遇同样的问题,

以后我的文章将遵循以下原则:

1.会在文章开头标明相关配置与环境

2.尽可能循序渐进(还是看不懂可能是我没有这样的天赋),标出阅读的前提知识

3.列出的代码自己先跑一遍

////////////////////////////////////////

语言:c++

前提知识:

1.类

2.继承

3.静态成员

集成开发环境:Visual Studio 2017

////////////////////////////////////////

文章:

所谓ebo,就是Empty Base Class Optimization,空基类最优化。

空类为没有非静态成员(此处的成员不包含函数)的类,也就是说空类中不包含占空间的成员,因为函数是不会影响类的大小的。

然后稍微解释一下,非静态成员,因为静态成员并不是存储在类对象的空间中,否则对象销毁时静态成员也会被销毁,所以静态成员是不会影响类的大小的。

空类的例子:

class Empty {};

class FuntionOnly

{

public:

void a() {}

int b() { return 1; }

int c(int n) { int m = n; return m; }

};

class StaticMemberOnly{ public:static int a; };

但是说是空类,其实sizeof(空类)=1,这表示其存在(根据其他博客所说,这也与大小的计算方法有关),但是这点空间是毫无意义的,因为它不存储数据,所以就有了ebo,空基类最优化。

空基类最优化:只有空类对象不与同一类型的其他对象或子对象分配在同一地址,就不需要为其分配空间。

可能有人说空类这只有1字节的大小,对程序影响不大,先不说积少成多,c++有一个特性字节对齐,比如说:

class Empty{}

class Test :public Empty

{

int a;

}

sizeof(Test)为多少呢,其为4,但是在没有ebo的情况下呢,其为8,这是因为c++的字节补齐,默认类的大小必须为类中大小最大的成员的倍数。

所以Empty大小为1,int大小为4,一共为5,所以补齐为8(4的倍数),当然你可以选择自定义对齐方式,但这不在这篇文章的讨论范围内。

1的补齐浪费空间是巨大的,当类中其他成员大小越大,补齐的空间就可能越大,这就是ebo的意义所在。

比如说:

class Empty {};

class Empty2:public Empty{};

class Empty3:public Empty2{};

class Empty4:public Empty, public Empty2{};//特殊案例

sizeof(Empty)=1

sizeof(Empty2)=1

sizeof(Empty3)=1

sizeof(Empty4)=1 //这个1的意义不一样,这个也是我写这篇东西的原因

前三个都好理解,他们就是ebo的体现(),那个1的大小只是为了标识类的存在,但是Empty4呢,其实它继承了两次Empty,不符合ebo的规则,父类Empty所以并没有进行最优化,但是实际上Empty4的大小仍为1,我上网查阅了其他博客,但是几乎没有提到这个问题,甚至有出现这个例子,其中还有将其大小写为2的,也许是不同编译器的处理问题,在这里我为跟我一样得到结果为1的同志们解释一下为何为1。

首先,类的继承,继承的原理为何,简单而言,其实就是往类中添加一个隐藏的被继承类的对象,并且编译器将被继承类的中被继承的成员当作继承类的成员,但是ebo只继承中会被使用,被包含时也不会使用。

比如

class Empty {};

class IncludeEmpty

{

public:

int a;

Empty empty;

};

class ExtendEmpty :Empty

{

public:

int a;

};

sizeof(IncludeEmpty)=8

sizeof(ExtendEmpty)=4

那么回来看Empty4,

class Empty4: public Empty,public Empty2{}

Empty4同时继承了Empty和Empty2,而Empty2又继承了Empty,所以实际上Empty4继承了两次Empty,为了查看父类地址,我往类中添加了几个函数(函数是不占类的大小的)

class Empty

{

public:

void pEmpty() { std::cout << "&Empty =" << this << std::endl; }

};

class Empty2 :public Empty

{

public:

void pEmpty2() { std::cout << "&Empty2=" << this << std::endl; }

};

class Empty4 :public Empty2, public Empty

{

public:

void pEmpty4() { std::cout << "&Empty4=" << this << std::endl; }

void pEmpty2_Empty() { std::cout << "Empty2::pEmpty() "; Empty2::pEmpty(); }

void pEmpty() { std::cout << "Empty::pEmpty() "; Empty::pEmpty(); }

};

然后产生对象:empty4

empty4.pEmpty4()        输出:&Empty4=00B9FB00

empty4.pEmpty2()        输出:&Empty2=00B9FB00

empty4.pEmpty2_Empty()  输出:&Empty2::pEmpty() &Empty=00B9FB00

empty4.pempty() 输出:Empty::pEmpty() &Empty=00B9FB01

发现父类Empty2中的Empty与父类Empty的地址不一致,那么将会产生1的大小(sizeof的结果其实是尾地址-首地址),其实这里已经没有在使用ebo,那1的大小是为了区分两个不同的Empty而存在的和前面的三个例子不同,

如果将Empty4改成如下就一目了然了:

class Empty4 :public Empty2, public Empty

{

public:

    int a;

void pEmpty4() { std::cout << "&Empty4=" << this << std::endl; }

void pEmpty2_Empty() { std::cout << "Empty2::pEmpty() "; Empty2::pEmpty(); }

void pEmpty() { std::cout << "Empty::pEmpty() "; Empty::pEmpty(); }

};

此时sizeof(Empty4)=8

很明显,空间超出了4而因为补齐而变为8。这就验证了ebo的准则:只有空类对象不与同一类型的其他对象或子对象分配在同一地址,就不需要为其分配空间。

接下来再谈谈同一地址的问题,即使是同一类型的对象只有分配地址不同,也不需要为其分配空间

再来两个类:

class Integer

{

public:

int a;

}

class Empty5:public Empty,public Integer,public Empty2{}

那么sizeof(Empty5)等于?

其实sizeof(Empty5)=4,仅仅只有Integer类的大小,也就是使用了ebo,因为Empty与Empty2中Empty分配的地址不同。这里Integer必须继承,而且位置必须在Empty与Empty2之间,只有这样才会影响它们的地址,

因为对象的初始化是从父对象开始的,而且初始化顺序与继承顺序一致。

结尾:

那么这篇文章的探讨到此为止,这是本人第一篇文章,只是一时兴起还请大家多多包含,指出错误。

你可能感兴趣的:(对于C++OBP的理解)