web-dev-qa-db-ja.com

メンバー変数のないC ++クラスがスペースを占めるのはなぜですか?

クラスがメンバー変数のない述語(または静的メンバー変数のみ)である場合でも、MSVCコンパイラーとGCCコンパイラーの両方が各クラスインスタンスごとに少なくとも1バイトを割り当てることがわかりました。次のコードは、ポイントを示しています。

#include <iostream>

class A
{
public:
   bool operator()(int x) const
   {
      return x>0;
   }
};

class B
{
public:
   static int v;
   static bool check(int x)
   {
      return x>0;
   }
};

int B::v = 0;

void test()
{
   A a;
   B b;
   std::cout << "sizeof(A)=" << sizeof(A) << "\n"
             << "sizeof(a)=" << sizeof(a) << "\n"
             << "sizeof(B)=" << sizeof(B) << "\n"
             << "sizeof(b)=" << sizeof(b) << "\n";
}

int main()
{
   test();
   return 0;
}

出力:

sizeof(A)=1
sizeof(a)=1
sizeof(B)=1
sizeof(b)=1

私の質問は、コンパイラがなぜそれを必要とするのですか?私が思い付くことができる唯一の理由は、すべてのメンバーvarポインターが異なることを確認して、タイプAまたはBの2つのメンバーを、それらへのポインターを比較することで区別できるようにすることです。しかし、小型コンテナを扱う場合、このコストは非常に厳しくなります。可能なデータアライメントを考慮して、変数なしでクラスあたり最大16バイトを取得できます(?!)。通常、いくつかのint値を保持するカスタムコンテナーがあるとします。次に、そのようなコンテナーの配列(約1000000メンバー)を検討します。オーバーヘッドは16 * 1000000になります!これが発生する典型的なケースは、比較述語がメンバー変数に格納されているコンテナークラスです。また、クラスインスタンスが常に一定の領域を占有する必要があることを考えると、A()(value)を呼び出すときにどのようなオーバーヘッドが予想されますか?

52
bkxp

C++標準からの不変式を満たす必要があります。同じタイプのすべてのC++オブジェクトは、識別できるように一意のアドレスを持つ必要があります。

オブジェクトがスペースを取らない場合、配列内のアイテムは同じアドレスを共有します。

74
Konrad Rudolph

基本的に、これは2つの要件の間の相互作用です。

  • 同じタイプの2つの異なるオブジェクトは、異なるアドレスにある必要があります。
  • 配列では、オブジェクト間にパディングがない場合があります。

最初の条件だけではゼロ以外のサイズは必要ないことに注意してください。

struct empty {};
struct foo { empty a, b; };

最初の要件は、サイズがゼロのaの後に、異なるアドレスを適用するための単一のパディングバイトが続き、その後にサイズのゼロのbを付けることで簡単に満たすことができます。ただし、

empty array[2];

異なるオブジェクト間のパディングempty[0]およびempty[1]は許可されません。

26
celtschk

すべての完全なオブジェクトには一意のアドレスが必要です。そのため、少なくとも1バイトのストレージ(アドレスのバイト)を使用する必要があります。

これが発生する可能性のある典型的なケースは、比較述語がメンバー変数に格納されているコンテナークラスです。

この場合、空の基本クラス最適化を使用できます。基本サブオブジェクトは、その一部である完全なオブジェクトと同じアドレスを持つことができるため、ストレージを占有することはありません。そのため、述語をメンバーではなく(おそらくプライベートな)基本クラスとしてクラスにアタッチできます。メンバーよりも扱いが少し面倒ですが、オーバーヘッドを排除する必要があります。

A()(value)を呼び出すときに予想されるオーバーヘッドのタイプは?

非メンバー関数の呼び出しと比較した場合の唯一のオーバーヘッドは、追加のthis引数を渡すことです。関数がインライン化されている場合は、これを削除する必要があります(通常、メンバー変数にアクセスしないメンバー関数を呼び出す場合と同様)。

13
Mike Seymour

主な質問に答える優れた答えがすでにあります。私はあなたが表明した懸念に対処したいと思います:

しかし、小型コンテナを扱う場合、このコストは非常に厳しくなります。可能なデータアライメントを考慮して、変数なしでクラスあたり最大16バイトを取得できます(?!)。通常、いくつかのint値を保持するカスタムコンテナーがあるとします。次に、そのようなコンテナーの配列(約1000000メンバー)を検討します。オーバーヘッドは16 * 1000000になります!これが発生する典型的なケースは、比較述語がメンバー変数に格納されているコンテナークラスです。

保有コストの回避A

コンテナのすべてのインスタンスがタイプAに依存している場合、Aのインスタンスをコンテナに保持する必要はありません。 Aのゼロ以外のサイズに関連するオーバーヘッドは、必要に応じてスタックにAのインスタンスを作成するだけで回避できます。

Aを保持するコストを回避できないこと

Aがポリモーフィックであると予想される場合、コンテナーの各インスタンスでAへのポインターを保持するように強制される場合があります。そのようなコンテナーの場合、各コンテナーのコストはポインターのサイズによって増加します。基本クラスAにメンバー変数があるかどうかは、コンテナーのサイズに影響しません。

sizeof Aの影響

どちらの場合でも、空のクラスのサイズは、コンテナーのストレージ要件に影響を与えません。

1
R Sahu