我正在阅读有关空基优化(EBO)的内容。在阅读时,我脑海中浮现出以下问题:
当对派生类没有任何贡献(既不是功能方面也不是数据方式)时,使用Empty类作为基类有什么意义?
在this article中,我读到了这个:
// S是空的 class struct T:S
{
int x;
};[...]
请注意,我们没有丢失任何数据或 代码准确性:当你创建一个 S类型的独立对象 对象的大小仍为1(或更多) 之前;仅当S用作基础时 另一个类的类确实记忆 足迹缩小到零。意识到 这种节约的影响,想象一下 包含125,000的向量 对象。 EBO单独节省了一半 兆字节的内存!
这是否意味着如果我们不使用“S”作为“T”的基类,我们必然消耗两倍的兆字节内存?我认为,这篇文章比较了两种我认为不正确的不同情景。
我想知道EBO可以被证明是有用的真实场景。(意思是,在同一场景中,如果我们不使用EBO,我们必然会亏本!)。
重点是空类的大小非零,但是在派生或派生时它的大小可以为零,那么我就不会问这个,正如我所知道的那样。我的问题是,为什么有人会从一个空洞的班级中获得他的班级? 即使他没有派遣并且只是写他的班级(没有任何空基础),他是否会以任何方式感到茫然?
答案 0 :(得分:34)
EBO在policy based design的上下文中非常重要,您通常会从多个策略类继承私有。如果我们以线程安全策略为例,可以想象伪代码:
class MTSafePolicy
{
public:
void lock() { mutex_.lock(); }
void unlock() { mutex_.unlock(); }
private:
Mutex mutex_;
};
class MTUnsafePolicy
{
public:
void lock() { /* no-op */ }
void unlock() { /* no-op */ }
};
给定基于策略的设计类,例如:
template<class ThreadSafetyPolicy>
class Test : ThreadSafetyPolicy
{
/* ... */
};
使用带有MTUnsafePolicy
的班级,只需在课程Test
中添加无大小开销:这是不为您不使用支付费用的完美示例。
答案 1 :(得分:6)
EBO实际上并不是一种优化(至少不是你在代码中所做的那种)。整点是空类的大小非零,但在派生或派生时,它的大小可以为零。
这是最常见的结果:
class A { };
class B { };
class C { };
class D : C { };
#include <iostream>
using namespace std;
int main()
{
cout << "sizeof(A) + sizeof(B) == " << sizeof(A)+sizeof(B) << endl;
cout << "sizeof(D) == " << sizeof(D) << endl;
return 0;
}
输出:
sizeof(A) + sizeof(B) == 2
sizeof(D) == 1
编辑: 优化是,如果你实际上派生(例如从仿函数,或从只有静态成员的类),你的类(即派生)的大小不会增加1(或更可能是4或8由于填充字节而导致。
答案 2 :(得分:5)
EBO中的“优化”意味着使用基类的情况可以优化为使用比使用相同类型的成员更少的内存。即你比较
struct T : S
{
int x;
};
与
struct T
{
S s;
int x;
};
不是
struct T
{
int x;
};
如果你的问题是为什么你会有一个空类(作为成员或作为基础),那是因为你使用它的成员函数。空意味着它没有数据成员,而不是它根本没有任何成员。这样的事情通常在使用模板编程时完成,其中基类有时是“空的”(没有数据成员),有时不是。
答案 3 :(得分:4)
当程序员想要在不增加客户端类大小的情况下向客户端公开某些数据时使用它。空类可以包含枚举和typedef,或者客户端可以使用的一些定义。使用这样一个类的最明智的方法是私有地继承这样的类。这将隐藏来自外部的数据,并且不会增加您的班级规模。
答案 4 :(得分:1)
EASTL对于他们为什么需要EBO有一个很好的解释,它也在他们链接到/ credit的文章中进行了深入解释
答案 5 :(得分:1)
可以有没有任何成员变量的空类,但可以作为实用程序类的成员函数(static
或 non static
),我们称之为 EmptyClass
。现在我们可以有一个案例,我们想要创建一个类(我们称之为 SomeClass
),它与 EmptyClass
有一种包含关系,但不是“is-a”关系。一种方法是在 EmptyClass
中创建类型为 SomeClass
的成员对象,如下所示:
class EmptyClass
{
public:
void someFun1();
static int someUtilityFun2();
};
//sizeof(EmptyClass) = 1
class SomeClass
{
private:
EmptyClass e;
int x;
};
//sizeof(SomeClass) = 8
现在由于一些对齐要求,编译器可能会向 SomeClass
添加填充,其大小现在为 8 字节。更好的解决方案是让 SomeClass
从 EmptyClass
私下派生,这样 SomeClass
将可以访问 EmptyClass
的所有成员函数,并且不会增加额外的大小通过填充。
class SomeClass : private EmptyClass
{
private:
int x;
}
//sizeof(SomeClass) = 4
答案 6 :(得分:0)
EBO不是程序员所影响的东西,和/或程序员会因为他选择不从空基类派生而受到惩罚。
编译器控制是否:
class X : emptyBase { int X; };
class Y { int x };
你得到sizeof(X) == sizeof(Y)
。如果这样做,编译器会实现EBO,否则就不会。
从来没有任何情况会发生sizeof(Y) > sizeof(X)
。
答案 7 :(得分:0)
大多数情况下,一个空的基类要么是多态的(文章提到的),要么是“tag”类,要么是异常类(尽管那些通常是从std :: exception派生的,它不是空的) 。有时候有一个很好的理由来开发一个以空基类开头的类层次结构。
Boost.CompressedPair使用EBO在其中一个元素为空的情况下缩小对象的大小。
答案 8 :(得分:-1)
我能想到的主要好处是dynamic_cast。您可以获取指向S的指针并尝试将其dynamic_cast转换为从S-继承的任何内容,假设S提供虚拟析构函数之类的虚函数,它几乎必须作为基类执行。如果你是,比如说,实现一种动态类型语言,你可能希望或者需要从基类派生纯粹出于类型擦除存储的目的,并通过dynamic_cast进行类型检查。