web-dev-qa-db-ja.com

静的constと#define

static constプリプロセッサよりも#define varsを使用する方が良いですか?それともコンテキストに依存しますか?

各方法の長所と短所は何ですか?

188

個人的には、プリプロセッサが嫌いなので、常にconstを使います。

#defineの主な利点は、実際にテキストをリテラル値に置き換えるだけであるため、プログラムに保存するためのメモリを必要としないことです。また、型がないという利点もあるため、警告を生成せずに任意の整数値に使用できます。

「定数」の利点は、スコープを設定できることと、オブジェクトへのポインターを渡す必要がある状況で使用できることです。

しかし、「静的」な部分で何が得られるのか正確にはわかりません。グローバルに宣言する場合は、静的を使用する代わりに、匿名の名前空間に配置します。例えば

namespace {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}
128
T.E.D.

用途に応じて、すべての長所と短所:

  • 列挙型
    • 整数値でのみ可能
    • 適切にスコープされた/識別子の衝突の問題、特にenum class Xの列挙がスコープX::によって明確にされるC++ 11 enumクラスでうまく処理されます
    • 強く型付けされていますが、C++ 03では制御できない、十分に大きい符号付きまたは符号なしのintサイズ(enumがstruct /のメンバーである場合、それらをパックするビットフィールドを指定できます) class/union)、C++ 11のデフォルトはintですが、プログラマは明示的に設定できます
    • アドレスを取得できません-列挙値が使用ポイントでインラインで効果的に置換されるため、アドレスはありません
    • より強力な使用制限(たとえば、増分-template <typename T> void f(T t) { cout << ++t; }はコンパイルされませんが、暗黙のコンストラクター、キャスト演算子、ユーザー定義演算子を使用して列挙型をクラスにラップできます)
    • 各定数の型は囲み列挙型から取得されるため、template <typename T> void f(T)は、異なる列挙型から同じ数値を渡されたときに異なるインスタンス化を取得します。これらはすべて、実際のf(int)インスタンス化とは異なります。各関数のオブジェクトコードは同一である可能性があります(アドレスオフセットは無視されます)が、コンパイラー/リンカーが不要なコピーを削除することは期待していませんが、コンパイラー/リンカーを確認することはできます。
    • typeof/decltypeを使用しても、numeric_limitsが意味のある値と組み合わせのセットに対する有用な洞察を提供することを期待することはできません(実際、「合法」の組み合わせはソースコードでも表記されません。enum { A = 1, B = 2 }-はA|Bからの「合法」ですプログラムロジックの視点?)
    • 列挙型のタイプ名は、RTTI、コンパイラメッセージなどのさまざまな場所に表示される場合があります。
    • 翻訳ユニットが実際に値を参照しないと列挙を使用できません。つまり、ライブラリAPIの列挙にはヘッダーで公開される値が必要です。また、makeおよびその他のタイムスタンプベースの再コンパイルツールは、変更時にクライアントの再コンパイルをトリガーします(不良!)
  • consts
    • 適切にスコープされた/識別子の衝突の問題がうまく処理されました
    • 強力な単一のユーザー指定タイプ
      • #define ala #define S std::string("abc")を「タイプ」しようとするかもしれませんが、定数は使用の各ポイントで異なる一時的なものの繰り返しの構築を避けます
    • 1つの定義ルールの複雑さ
    • アドレスを取得したり、それらへのconst参照を作成したりできます。
    • const以外の値に最も似ており、2つを切り替える場合の作業と影響を最小限に抑えます
    • 値は実装ファイル内に配置でき、ローカライズされた再コンパイルとクライアントリンクのみが変更を取得できるようにします。
  • 定義
    • 「グローバル」スコープ/使用法の競合が発生しやすく、正常なエラーメッセージではなく、解決が困難なコンパイルの問題や予期しない実行時の結果が生じる可能性があります。これを緩和するには以下が必要です。
      • 長く、不明瞭な、および/または中央で調整された識別子、およびそれらへのアクセスは、暗黙的に一致する使用済み/現在/ Koenigルックアップ名前空間、名前空間エイリアスなどから恩恵を受けることはできません.
      • トランプのベストプラクティスでは、テンプレートパラメーター識別子を1文字の大文字(おそらく数字が後に続く)にすることができますが、小文字を使用しない識別子の他の使用は、プリプロセッサ定義(OSおよびC/C++ライブラリ以外)ヘッダー)。これは、企業規模のプリプロセッサの使用が管理可能なままであるために重要です。サードパーティのライブラリが準拠することが期待できます。これを観察することは、既存のconstまたはenumの定義へ/からの移行が大文字の変更を伴うことを意味するため、「単純な」再コンパイルではなく、クライアントソースコードの編集が必要です。 (個人的には、列挙型の最初の文字を大文字にしますが、定数は使用しません。そのため、これら2つの間を移行することになります。
    • より多くのコンパイル時操作が可能:文字列リテラルの連結、文字列化(そのサイズを取る)、識別子への連結
      • 欠点は、#define X "x"といくつかのクライアントの使用法が"pre" X "post"である場合、Xを定数ではなくランタイム変更可能な変数にしたい、または必要とする場合、クライアントコードを(単に再コンパイルするのではなく)強制的に編集しますが、その遷移はconst char*から簡単ですまたは、ユーザーに連結操作を組み込むことを既に強制している場合は、const std::string(たとえば、stringの場合は"pre" + X + "post"
    • 定義済みの数値リテラルでsizeofを直接使用することはできません
    • 型なし(unsignedと比較してもGCCは警告しません)
    • 一部のコンパイラ/リンカー/デバッガーチェーンは識別子を提示しない場合があるため、「マジックナンバー」(文字列など)を見るだけになります。
    • 住所を取ることができません
    • #defineが作成されるコンテキストでは、使用される各ポイントで評価されるため、置換された値は正当(または離散)である必要はないため、まだ宣言されていないオブジェクトを参照できます。事前に含めて、配列の初期化に使用できる{ 1, 2 }#define MICROSECONDS *1E-6などの「定数」を作成します(definitelyこれはお勧めしません!)
    • __FILE____LINE__などの特別なものをマクロ置換に組み込むことができます
    • 条件付きでコードを含めるための#ifステートメントの存在と値をテストできます(プリプロセッサによって選択されない場合、コードをコンパイルする必要がないため、ポストプリプロセスの「if」よりも強力です)、#undef- ine、再定義などを使用します.
    • 置換されたテキストは公開する必要があります:
      • クライアントが使用するライブラリ内のマクロはヘッダーにある必要があることを意味します。したがって、makeおよびその他のタイムスタンプベースの再コンパイルツールは、変更時にクライアントの再コンパイルをトリガーします(悪い!)
      • またはコマンドラインで、クライアントコードを確実に再コンパイルするためにさらに注意が必要です(たとえば、定義を提供するMakefileまたはスクリプトを依存関係としてリストする必要があります)

一般的なルールとして、私はconstsを使用し、それらを一般的な使用のための最もプロフェッショナルなオプションと考えます(ただし、他の人はこの古い怠け者のプログラマーに魅力的なシンプルさを持っています)。

221
Tony Delroy

これがC++の質問であり、代替として#defineに言及している場合、それはクラスメンバーではなく「グローバル」(つまりファイルスコープ)定数に関するものです。 C++のそのような定数に関してはstatic constは冗長です。 C++では、constにはデフォルトで内部リンケージがあり、staticを宣言しても意味がありません。つまり、実際にはconst#defineについてです。

そして最後に、C++ではconstが望ましいです。少なくとも、そのような定数は型指定されスコープされているためです。いくつかの例外を除き、constよりも#defineを好む理由はまったくありません。

文字列定数BTWは、このような例外の一例です。 #defined文字列定数を使用すると、C/C++コンパイラのコンパイル時の連結機能を次のように使用できます。

#define OUT_NAME "output"
#define LOG_EXT ".log"
#define TEXT_EXT ".txt"

const char *const log_file_name = OUT_NAME LOG_EXT;
const char *const text_file_name = OUT_NAME TEXT_EXT;

追伸繰り返しますが、念のために、誰かがstatic const#defineの代替として言及するとき、それは通常、彼らがC++ではなくCについて話していることを意味します。この質問は適切にタグ付けされているのだろうか...

42
AnT

静的constを使用することは、コードで他のconst変数を使用することに似ています。これは、プリコンパイルプロセスでコード内で単純に置き換えられる#defineとは対照的に、情報がどこから来てもトレースできることを意味します。

この質問については、C++ FAQ Liteをご覧ください。 http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7

5
Percutio
  • 静的constは型付けされ(型を持ち)、コンパイラによって有効性、再定義などをチェックできます。
  • #defineは何でも未定義に再定義できます。

通常、静的constを優先する必要があります。欠点はありません。 prprocessorは、主に条件付きコンパイルに使用する必要があります(場合によっては、非常にダーティなトリックにも使用します)。

4
RED SOFT ADAIR

プリプロセッサディレクティブ#defineを使用して定数を定義することは、C++だけでなくCにも適用することはお勧めしません。これらの定数には型がありません。 Cでさえ、定数にconstを使用することが提案されました。

2
Aleksey Bykov

こちらをご覧ください: static const vs define

通常はconst宣言(静的である必要はないことに注意してください)

2
ennuikiller

プリプロセッサなどのいくつかの追加ツールよりも、常に言語機能を使用することをお勧めします。

ES.31:定数または「関数」にマクロを使用しないでください

マクロはバグの主な原因です。マクロは、通常のスコープとタイプの規則に従いません。マクロは、引数を渡すための通常の規則に従いません。マクロは、人間の読者がコンパイラが見るものとは異なるものを見るようにします。マクロはツールの構築を複雑にします。

From C++ Core Guidelines

1
Hitokage

#defineは予期しない結果につながる可能性があります。

#include <iostream>

#define x 500
#define y x + 5

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

誤った結果を出力します:

y is 505
z is 510

ただし、これを定数に置き換えた場合:

#include <iostream>

const int x = 500;
const int y = x + 5;

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

正しい結果を出力します:

y is 505
z is 1010

これは、#defineが単にテキストを置き換えるためです。これを行うと、操作の順序が著しく損なわれる可能性があるため、代わりに定数変数を使用することをお勧めします。

1
Juniorized

クラスのすべてのインスタンス間で共有される定数を定義する場合は、静的constを使用します。定数が各インスタンスに固有の場合は、constを使用します(ただし、クラスのすべてのコンストラクターは、初期化リストでこのconstメンバー変数を初期化する必要があります)。

0
snr