定数値をクラスに関連付けたい場合、同じ目標を達成するための2つの方法があります。
class Foo
{
public:
static const size_t Life = 42;
};
class Bar
{
public:
enum {Life = 42};
};
構文的にも意味的にも、クライアントの観点からは同じように見えます。
size_t fooLife = Foo::Life;
size_t barLife = Bar::Life;
純粋なスタイル以外に、あるものが別のものよりも好まれる理由はありますか?
多くのコンパイラが値のインプレース初期化をサポートしていなかったため、enum
ハックが必要でした。これはもはや問題ではないので、他のオプションを選択してください。最新のコンパイラは、この定数を最適化して、ストレージスペースを必要としないようにすることもできます。
static const
バリアントを使用しない唯一の理由は、禁止値のアドレスを取得する場合です。enum
値のアドレスを取得することはできません。定数のアドレスを取得できます(これにより、コンパイラに値用のスペースを予約するように求められますが、実際に取得されるのはifのみです)。
さらに、定数が明示的にdefinedでない限り、アドレスを取得するとリンク時エラーが発生します。宣言のサイトでまだ初期化できることに注意してください。
struct foo {
static int const bar = 42; // Declaration, initialization.
};
int const foo::bar; // Definition.
それらは同一ではありません:
size_t *pLife1 = &Foo::Life;
size_t *pLife2 = &Bar::Life;
1つの違いは、列挙型が、たとえば、より適切な型チェックを取得するために、メソッドパラメーターとして使用できる型を定義することです。どちらもコンパイラによってコンパイル時定数として扱われるため、同一のコードを生成する必要があります。
static const
値は、表示されるコードの99%でenum
と同じようにr値として扱われます。定数r値には、メモリが生成されることはありません。 enum
定数の利点は、他の1%ではl値になれないことです。 static const
値はタイプセーフであり、float、c-stringなどを使用できます。
コンパイラは、メモリが関連付けられている場合、Foo::Life
をl値にします。そのための通常の方法は、そのアドレスを取得することです。例えば&Foo::Life;
GCCがアドレスを使用する微妙な例を次に示します。
int foo = Rand()? Foo::Life: Foo::Everthing;
コンパイラによって生成されたコードは、Life
およびEverything
のアドレスを使用します。さらに悪いことに、これはFoo::Life
とFoo::Everything
の欠落しているアドレスに関するリンカーエラーのみを生成します。この動作は完全に標準に準拠していますが、明らかに望ましくありません。これが発生する可能性のあるコンパイラ固有の方法は他にもあり、すべての標準に準拠しています。
準拠するc ++ 11コンパイラを入手すると、正しいコードは次のようになります。
class Foo {
public:
constexpr size_t Life = 42;
};
これは常にl値であることが保証されており、タイプセーフであり、両方の長所があります。
微妙な違いの1つは、列挙型をヘッダーで定義し、すべてのユーザーに表示する必要があることです。依存関係を回避している場合、これは苦痛です。たとえば、PImplでは、列挙型を追加することはやや逆効果です。
// MyPImpl.hpp
class MyImpl ;
class MyPimpl
{
public :
enum { Life = 42 } ;
private :
MyImpl * myImpl ;
}
別の番目の解決策は、質問で提案された「const static」代替案のバリエーションです。ヘッダーで変数を宣言しますが、ソースで定義します。
// MyPImpl.hpp
class MyImpl ;
class MyPimpl
{
public :
static const int Life ;
private :
MyImpl * myImpl ;
}
。
// MyPImpl.cpp
const int MyPImpl::Life = 42 ;
MyPImpl :: Lifeの値は、MyPImplのユーザー(MyPImpl.hppを含む)には表示されないことに注意してください。
これにより、MyPimplの作成者は、PImplの全体的な目的のように、MyPImplユーザーが再コンパイルしなくても、必要に応じて「Life」の値を変更できます。
必要に応じて、静的constメンバー値のアドレスを取得できます。アドレスを取得するには、列挙型の個別のメンバー変数を宣言する必要があります。
列挙型ハックは、いくつかの理由で知っておく価値があります。まず、列挙型ハックは、constよりも#defineのように動作しますが、それが必要な場合もあります。たとえば、constのアドレスを取得することは合法ですが、列挙型のアドレスを取得することは合法ではありません。また、通常、#defineのアドレスを取得することも合法ではありません。整数定数の1つへのポインターまたは参照を他のユーザーに取得させたくない場合は、列挙型がその制約を適用するための良い方法です。 (コーディングの決定を通じて設計制約を適用する方法の詳細については、項目18を参照してください。)また、優れたコンパイラーは整数型のconstオブジェクト用のストレージを確保しませんが(オブジェクトへのポインターまたは参照を作成しない限り)、ずさんなコンパイラーはそして、あなたはそのようなオブジェクトのためにメモリを取っておくことをいとわないかもしれません。 #definesと同様に、列挙型がそのような不要なメモリ割り当てを引き起こすことはありません。
効果的なc ++ブック