25

Empty Base Optimization (EBO) について読んでいました。読んでいて、次のような疑問が頭に浮かびました。

  1. 派生クラスに何も貢献しない場合 (機能的にもデータ的にも)、空のクラスを基本クラスとして使用するポイントは何ですか?

  2. この記事で、私はこれを読みました:

//S は空
のクラス struct T : S
{
      int x;
};

[...]

データやコードの精度は失われていないことに注意してください。タイプ S のスタンドアロン オブジェクトを作成する場合、オブジェクトのサイズは以前と同様に 1 (またはそれ以上) のままです。S が別のクラスの基本クラスとして使用される場合にのみ、そのメモリ フットプリントはゼロに縮小されます。この節約の影響を理解するには、125,000 個のオブジェクトを含むベクトルを想像してください。EBO だけで、0.5 メガバイトのメモリを節約できます。

「S」を「T」の基本クラスとして使用しない場合、必然的に 2 倍のメガバイトのメモリを消費するということですか? この記事は、私が正しくないと思う 2 つの異なるシナリオを比較していると思います。

EBOが有用であると証明できる実際のシナリオを知りたいです。

回答に次のような説明が含まれている場合は注意してください。

全体的なポイントは、空のクラスのサイズがゼロではないということですが、派生または派生するときにサイズがゼロになる可能性があるため、既に知っているので、それを求めていません。私の質問は、そもそも空のクラスから自分のクラスを派生させるのはなぜですか? 彼がクラスを派生せずに (空のベースなしで) 単純に記述したとしても、彼は何か途方に暮れていますか?

4

9 に答える 9

45

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の完璧な例です。

于 2010-12-01T14:57:42.013 に答える
7

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 の可能性が高い)。

于 2010-12-01T14:19:30.773 に答える
6

EBOの「最適化」とは、基本クラスを使用する場合に、同じタイプのメンバーを使用する場合よりも少ないメモリを使用するように最適化できることを意味します。つまり、あなたは比較します

struct T : S 
{
      int x;
};

struct T
{
      S s;
      int x;
};

ではない

struct T
{
      int x;
};

なぜ(メンバーとして、またはベースとして)空のクラスがあるのか​​という質問の場合、それはそのメンバー関数を使用しているためです。空とは、データメンバーがないことを意味し、メンバーがまったくないことを意味しません。このようなことは、基本クラスが「空」(データメンバーなし)である場合とそうでない場合があるテンプレートを使用してプログラミングするときによく行われます。

于 2010-12-01T14:56:58.867 に答える
5

これは、プログラマーがクライアント クラスのサイズを増やさずに一部のデータをクライアントに公開したい場合に使用されます。空のクラスには、列挙型と型定義、またはクライアントが使用できるいくつかの定義を含めることができます。そのようなクラスを使用する最も賢明な方法は、そのようなクラスをプライベートに継承することです。これにより、データが外部から隠され、クラスのサイズが増加しません。

于 2012-10-23T18:24:35.037 に答える
1

ほとんどの場合、空の基本クラスは多態的に (記事で言及されています)、「タグ」クラスとして、または例外クラスとして使用されます (ただし、これらは通常、空ではない std::exception から派生します)。空の基本クラスから始まるクラス階層を作成する正当な理由がある場合があります。

Boost.CompressedPair は、要素の 1 つが空の場合に、EBO を使用してオブジェクトのサイズを縮小します。

于 2010-12-01T14:36:15.257 に答える
1

EASTLは、EBO が必要な理由について十分に説明しています。また、リンク先/クレジットの論文でも詳しく説明されています。

于 2010-12-01T14:45:00.677 に答える
0

EBO はプログラマーが影響を与えるものではありません。また、プログラマーが空の基本クラスから派生しないことを選択した場合、プログラマーは罰せられます。

コンパイラは、次のことを制御します。

class X : emptyBase { int X; };
class Y { int x };

あなたが得るsizeof(X) == sizeof(Y)かどうか。実行する場合、コンパイラは EBO を実装し、そうでない場合は実装しません。

発生する状況は決してありませんsizeof(Y) > sizeof(X)

于 2010-12-01T14:20:56.633 に答える
-1

私が考えることができる主な利点は、dynamic_cast です。S へのポインターを取り、それを S から継承するものに dynamic_cast しようとすることができます。S が仮想デストラクタのような仮想関数を提供すると仮定すると、基本クラスとして実行する必要があります。たとえば、動的に型指定された言語を実装している場合、すべての型が純粋に型消去されたストレージと dynamic_cast による型チェックの目的で基本クラスから派生することを望み、または必要とする場合があります。

于 2010-12-01T14:52:36.437 に答える