C++ 程序员何时使用空基优化(EBO)

C++ 程序员何时使用空基优化(EBO),c++,class,optimization,memory-management,compiler-optimization,C++,Class,Optimization,Memory Management,Compiler Optimization,我在读关于空基优化(EBO)的书。在阅读过程中,我突然想到了以下问题: 当空类对派生类没有贡献时(无论是功能方面还是数据方面)使用空类作为基类有什么意义 在这本书中,我读到: //S为空 类结构T:S { int x; }; [……] 请注意,我们没有丢失任何数据或数据 代码准确性:当您创建 类型为S的独立对象 对象的大小仍为1(或更大),如图所示 之前仅当S用作基础时 另一个类的类不使用其内存 足迹缩小到零。认识到 这种储蓄的影响,想象一下 包含125000的向量 对象仅EBO就可以节省半

我在读关于空基优化(EBO)的书。在阅读过程中,我突然想到了以下问题:

  • 空类对派生类没有贡献时(无论是功能方面还是数据方面)使用空类作为基类有什么意义

  • 在这本书中,我读到:

  • //S为空
    类结构T:S
    {
    int x;
    };

    [……]

    请注意,我们没有丢失任何数据或数据 代码准确性:当您创建 类型为S的独立对象 对象的大小仍为1(或更大),如图所示 之前仅当S用作基础时 另一个类的类不使用其内存 足迹缩小到零。认识到 这种储蓄的影响,想象一下 包含125000的向量 对象仅EBO就可以节省半个小时 兆字节的内存

    这是否意味着如果我们不使用“S”作为“t”的基类,我们必然会消耗双倍的内存?我认为,这篇文章比较了两种不同的情况,我认为这是不正确的

    我想知道一个真实的场景,当EBO被证明是有用的(这意味着,在同一场景中,如果我们不使用EBO,我们必然会不知所措!)

    请注意,如果您的答案包含如下解释:
    关键是一个空类的大小不是零,但是当派生或派生它时,它的大小可以是零,那么我不是在问这个问题,因为我已经知道了。我的问题是,为什么会有人首先从一个空类派生出他的类即使他没有派生并简单地编写他的类(没有任何空基),他在任何方面都不知所措吗?

    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;
    }
    
    要编辑:
    优化是,如果您确实派生(例如,从一个函子,或者从一个只有静态成员的类派生),那么您的类(派生的)的大小不会增加1(或者更可能是4或8,因为填充字节)。

    EBO不是程序员影响的东西,并且/或者程序员会因为if(s)而受到惩罚他选择不从空基类派生

    编译器控制是否用于:

    class X : emptyBase { int X; };
    class Y { int x };
    
    您是否得到了
    sizeof(X)==sizeof(Y)
    。如果是,编译器将实现EBO,如果不是,则不会


    从来没有任何情况下会出现
    sizeof(Y)>sizeof(X)

    大多数情况下,空基类要么以多态方式使用(本文提到),要么作为“标记”类,要么作为异常类(尽管这些类通常是从std::exception派生的,它不是空的)。有时,开发一个以空基类开始的类层次结构是有充分理由的


    Boost.CompressedPair在其中一个元素为空的情况下使用EBO缩小对象的大小。

    很好地解释了他们为什么需要EBO,这在他们链接到/credit的论文中也作了深入解释。

    我能想到的主要好处是动态投射。您可以获取指向S的指针,并尝试将其动态转换为从S继承的任何对象—假设S提供了虚拟函数,如虚拟析构函数,它几乎必须作为基类来执行。例如,如果您正在实现一种动态类型化语言,那么您很可能希望或需要每个类型都从基类派生出来,纯粹是为了类型擦除存储和通过动态转换进行类型检查在EBO中,表示当您使用基类时,可以优化以使用比使用相同类型的成员更少的内存。也就是说,你比较

    struct T : S 
    {
          int x;
    };
    

    不与

    struct T
    {
          int x;
    };
    

    如果您的问题是为什么会有一个空类(作为成员或基),那是因为您使用了它的成员函数。空表示它没有数据成员,而不是说它根本没有任何成员。在使用模板编程时,通常会执行类似的操作,其中基类有时为“空”(无数据成员),有时为空。

    EBO在这样的环境中很重要,您通常从多个策略类中私下继承。如果我们以线程安全策略为例,可以想象伪代码:

    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
    中添加无大小开销:这是一个完美的例子,说明了不要为不使用的东西付费。

    当程序员想要向客户机公开一些数据而不增加客户机类大小时,可以使用它。空类可以包含enum和typedef,或者客户端可以使用的一些定义。使用此类最明智的方法是私有地继承此类。这将从外部隐藏数据,并且不会增加您的班级规模。

    @Let_Me_。。。我编辑了我的问题。请再看一遍。。我希望您现在能理解我的要求。:-)@让我…谢谢你的更新。。这是有道理的。我正在寻找更多的回应…更多的场景。@Nawaz你所说的场景到底是什么意思?@Nawaz所以你想知道如何才能拥有一个空类?它不能包含任何非静态属性或虚拟函数。使用选项几乎是无限的。例如:朋友注入,或者通常是对奇怪的循环模式(比如实例计数器)的最聪明的使用。。顺便说一句,从一个“只有”静态成员的类派生类有什么好处?我看不出有什么意义:-/。。。这篇文章太大了。。。当我有足够的时间时,我会读它。然后我会评论它…:-)。。。谢谢你的链接,顺便说一句。文章中的一些引文将有助于防止链接腐烂(并使之更容易)
    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
    {
      /* ... */
    };