静的constと#define


212

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

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


14
スコット・マイヤーズはこの主題を非常にうまくそして徹底的にカバーしています。「Effective C ++ Third Edition」の彼のアイテム#2。2つの特殊なケース(1)静的constは、クラス固有の定数のクラススコープ内で優先されます。(2)#defineよりも名前空間または匿名スコープconstが推奨されます。
エリック

2
私は列挙型を好む。両方のハイブリッドだからです。変数を作成しない限り、スペースを占有しません。定数としてのみ使用する場合は、enumが最適なオプションです。C / C ++ 11 stdでの型安全性と完全な定数も備えています。#defineはタイプunsafeです。コンパイラーが最適化できない場合、constはスペースを取ります。
siddhusingh 2013

1
私の決断使用するかどう#definestatic constによって駆動される(文字列の)初期化(それは以下の回答を通じて言及されていなかった)側面:定数は、特定のコンパイル単位内で使用されている場合にのみ、私は一緒に行くstatic const、他の私の使用#define-回避の静的順序の初期化大失敗 isocpp.org/wiki/faq/ctors#static-init-order
マーティン・ドヴォルザーク

場合constconstexprまたはenumまたはあなたのケースの変動作品、そしてそれを好む#define
Phil1970

@MartinDvorak " 静的な順序の初期化の大失敗を避ける "どのように定数の問題ですか?
curiousguy

回答:


139

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

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

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

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

namespace {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}

8
文字列定数は、具体的には#define、少なくともそれらがより大きな文字列定数の「構成要素」として使用できる場合、dであることから利益を得る可能性がある定数の1つです。例については、私の返信を参照してください。
AnT 2009年

62
#define任意のメモリを使用していないことの利点は不正確です。例の「60」は、static constまたはに関係なく、どこかに保存する必要があり#defineます。実際、#defineを使用すると大量の(読み取り専用)メモリ消費が発生し、静的なconstが不要なメモリを使用しないコンパイラを見てきました。
Gilad Naor

3
#defineは入力したようなものなので、メモリからのものではありません。
牧師は、

27
@theReverendリテラル値は、どういうわけかマシンリソースの消費を免除されますか?いいえ、さまざまな方法で使用される可能性があります。スタックやヒープに表示されない可能性がありますが、プログラムは、コンパイルされたすべての値とともにメモリにロードされます。
Sqeaky 2013

13
@ gilad-naor、あなたは一般的に正しいですが、60のような小さな整数は実際にはある種の部分的な例外である場合があります。一部の命令セットには、整数または整数のサブセットを命令ストリームに直接エンコードする機能があります。たとえば、MIPは即時に追加します(cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/addi.html)。この種の場合、#defineされた整数は、コンパイルされたバイナリではとにかく存在しなければならなかった命令のいくつかのスペアビットを占有するため、スペースをまったく使用しないと言えます。
ahcox 2015年

241

#defines、consts、および(忘れてしまった)enumsの長所と短所、使用方法による:

  1. enums:

    • 整数値でのみ可能
    • 適切にスコープされた/識別子の衝突の問題は、特に列挙enum class Xがスコープによって明確にされるC ++ 11列挙クラスで適切に処理されましたX::
    • 強く型付けされていますが、C ++ 03で制御できない、十分に大きな符号付きまたは符号なしのintサイズです(ただし、列挙型が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プログラムロジックから「合法」視点?)
    • enumのタイプ名は、RTTI、コンパイラメッセージなどのさまざまな場所に表示される可能性があります。
    • 翻訳ユニットが実際に値を見ない限り、列挙型を使用することはできません。つまり、ライブラリAPIの列挙型はヘッダーで公開されている値を必要としmake、他のタイムスタンプベースの再コンパイルツールは、それらが変更されたときにクライアントの再コンパイルをトリガーします(悪い! )

  1. consts:

    • 適切にスコープされた/識別子の衝突の問題は適切に処理されました
    • 強力な単一のユーザー指定タイプ
      • あなたは#defineala を「タイプ」しようとするかもしれません#define S std::string("abc")が、定数は使用の各ポイントで別個の一時的なものを繰り返し構築することを避けます
    • 1つの定義ルールの複雑化
    • アドレスを取得したり、それらへのconst参照を作成したりできます。
    • const値に最も類似しており、2つを切り替えた場合の作業と影響を最小限に抑えます。
    • 値を実装ファイル内に配置できるため、ローカライズされた再コンパイルとクライアントリンクのみで変更を取得できます

  1. #defines:

    • 「グローバル」スコープ/競合する使用法の傾向があり、これにより、正しいエラーメッセージではなく、解決が困難なコンパイルの問題と予期しない実行時の結果が生成される可能性があります。これを軽減するには、以下が必要です。
      • 長い、あいまいな、および/または中央で調整された識別子、およびそれらへのアクセスは、暗黙的に一致するused / current / Koenig-looked-up名前空間、名前空間エイリアスなどから利益を得ることができません。
      • トランプのベストプラクティスでは、テンプレートパラメーター識別子を1文字の大文字(場合によっては数字が続く)にすることができますが、小文字のない識別子の他の使用は、通常、プリプロセッサ定義(OSおよびC / C ++ライブラリ以外)のために予約され、予期されますヘッダー)。これは、エンタープライズ規模のプリプロセッサーの使用を管理し続けるために重要です。サードパーティのライブラリは準拠することが期待できます。これを確認すると、既存のconstまたは列挙型と定義の間の移行には大文字と小文字の変更が含まれるため、「単純な」再コンパイルではなくクライアントのソースコードを編集する必要があります。(個人的に、私は列挙の最初の文字を大文字にしますが、定数は大文字にしません。そのため、これら2つの間の移行もヒットします-おそらくそれを再考する時間です。)
    • より多くのコンパイル時の操作が可能:文字列リテラル連結、文字列化(そのサイズを取る)、識別子への連結
      • 欠点は、与えられたということです#define X "x"と、いくつかのクライアントの使用ALA "pre" X "post"希望やXランタイム変更可能変数ではなく、一定にする必要がある場合はその移行がより容易になり、一方、あなたは、(というだけで再コンパイルより)クライアントコードを編集を強制const char*たりconst std::string、彼らが与えられましたすでに(例えば連結演算を組み込むために、ユーザーを強制"pre" + X + "post"するためにstring
    • sizeof定義された数値リテラルを直接使用することはできません
    • 型なし(GCCはと比較しても警告しませんunsigned
    • 一部のコンパイラ/リンカー/デバッガチェーンでは識別子が表示されない場合があるため、「マジックナンバー」(文字列など)を見ることになります。
    • 住所を取得できません
    • 置換された値は、#defineが作成されるコンテキストで有効である(または個別である)必要はありません。使用の各ポイントで評価されるため、まだ宣言されていないオブジェクトを参照でき、必要のない「実装」に依存します。あらかじめ含まれている、{ 1, 2 }配列の初期化に使用できるような「定数」などを作成します#define MICROSECONDS *1E-6(これは絶対にお勧めしません!)
    • のような特別なもの__FILE____LINE__、マクロ置換に組み込むことができるもの
    • #ifコードの条件付きインクルードのステートメントの存在と値をテストできます(プリプロセッサーで選択されない場合、コードをコンパイルする必要がないため、前処理の「if」よりも強力です)、#undef-ine、redefineなどを使用します。
    • 置換されたテキストを公開する必要があります:
      • クライアントが使用するライブラリのマクロはヘッダーに含まれている必要があるためmake、他のタイムスタンプベースの再コンパイルツールは、変更されたときにクライアントの再コンパイルをトリガーします(悪い!)
      • または、コマンドラインで、クライアントコードを確実に再コンパイルするためにさらに注意が必要です(たとえば、定義を提供するMakefileまたはスクリプトを依存関係としてリストする必要があります)

私の個人的な意見:

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


1
素晴らしい答え。1つの小さなnit:小さなステートマシンなどのように、コードを明確にするために、ヘッダーにないローカル列挙型を使用することがあります。したがって、常にヘッダーに含める必要はありません。
kert

長所と短所が混ざっているので、比較表をぜひご覧ください。
Unknown123

@ Unknown123:自由に投稿してください-ここから価値があると感じたポイントをはぎ取ってもかまいません。乾杯
トニー・デルロイ

48

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

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

文字列定数、BTWは、このような例外の一例です。#defineD列定数1は、のように、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;

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


1
単に#defineを好む理由はもない」ヘッダーファイルで定義された静的変数?
curiousguy

9

#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
私は別の予期しない結果がありました: y5500、リトルエンディアン連結x5と
Hammerを使用したコード


4
  • 静的constは型指定されており(型を持っています)、コンパイラーによって妥当性、再定義などをチェックできます。
  • #defineは、未定義のまま再定義できます。

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


3

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



2

プリプロセッサのようないくつかの追加ツールよりも常に言語機能を使用することを好みます。

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

マクロはバグの主な原因です。マクロは通常のスコープとタイプのルールに従いません。マクロは、引数の受け渡しに関する通常の規則に従いません。マクロは、人間の読み手がコンパイラとは異なる何かを見ることを保証します。マクロはツールの構築を複雑にします。

C ++コアガイドライン


0

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.