Constexprとマクロ


95

マクロの使用をどこで好むべきで、constexprをどこで好むべきですか?基本的に同じではないですか?

#define MAX_HEIGHT 720

vs

constexpr unsigned int max_height = 720;

4
AFAIK constexprは、より多くの型安全性を提供します
コード-見習い

14
簡単:constexr、常に。
N。「代名詞」m。

ご質問のいくつかに答えるかもしれないstackoverflow.com/q/4748083/540286
Ortwin Angermeier

回答:


151

基本的に同じではないですか?

いいえ、絶対にありません。程遠い。

あなたのマクロがでありint、あなたconstexpr unsignedがであるという事実とは別に、unsigned重要な違いがあり、マクロには1つの利点しかありません。

範囲

マクロはプリプロセッサによって定義され、発生するたびにコードに置き換えられるだけです。プリプロセッサはばかげており、C ++の構文やセマンティクスを理解していません。マクロは名前空間、クラス、ファンクションブロックなどのスコープを無視するため、ソースファイル内の他の名前を使用することはできません。これは、適切なC ++変数として定義された定数には当てはまりません。

#define MAX_HEIGHT 720
constexpr int max_height = 720;

class Window {
  // ...
  int max_height;
};

max_heightクラスメンバーであり、スコープが異なり、名前空間スコープのものとは異なるため、メンバー変数を呼び出すことは問題ありません。MAX_HEIGHTメンバーの名前を再利用しようとすると、プリプロセッサはそれをコンパイルされないこのナンセンスに変更します。

class Window {
  // ...
  int 720;
};

これが、マクロUGLY_SHOUTY_NAMESを目立たせるために指定する必要がある理由であり、衝突を避けるためにマクロに名前を付ける際には注意が必要です。マクロを不必要に使用しない場合は、それについて心配する必要はありません(そして読む必要もありませんSHOUTY_NAMES)。

関数内に定数が必要な場合は、マクロを使用してそれを行うことはできません。プリプロセッサは関数が何であるか、または関数内にあることの意味を知らないためです。マクロをファイルの特定の部分のみに制限するには、マクロを#undef再度制限する必要があります。

int limit(int height) {
#define MAX_HEIGHT 720
  return std::max(height, MAX_HEIGHT);
#undef MAX_HEIGHT
}

はるかに賢明なものと比較してください:

int limit(int height) {
  constexpr int max_height = 720;
  return std::max(height, max_height);
}

なぜあなたはマクロのものを好むのですか?

実際のメモリの場所

constexpr変数は変数であるため、実際にはプログラムに存在し、アドレスを取得して参照をバインドするなど、通常のC ++処理を実行できます。

このコードの動作は未定義です。

#define MAX_HEIGHT 720
int limit(int height) {
  const int& h = std::max(height, MAX_HEIGHT);
  // ...
  return h;
}

問題はそれMAX_HEIGHTが変数ではないことです。そのため、std::max一時的な呼び出しintはコンパイラーによって作成される必要があります。によって返される参照はstd::max、そのステートメントの終了後に存在しない一時的なものを参照する可能性があるため、return h無効なメモリにアクセスします。

この問題は、適切な変数では存在しません。これは、メモリ内の固定された場所が消えないためです。

int limit(int height) {
  constexpr int max_height = 720;
  const int& h = std::max(height, max_height);
  // ...
  return h;
}

(実際には、おそらく宣言しint hないでしょうconst int& hが、問題はより微妙な状況で発生する可能性があります。)

プリプロセッサの状態

マクロを優先するのは、#if条件で使用するために、プリプロセッサがその値を理解する必要がある場合のみです。

#define MAX_HEIGHT 720
#if MAX_HEIGHT < 256
using height_type = unsigned char;
#else
using height_type = unsigned int;
#endif

プリプロセッサが変数を名前で参照する方法を理解していないため、ここでは変数を使用できませんでした。マクロ展開や##includeand#define#if)で始まるディレクティブなどの基本的な非常に基本的なことだけを理解します。

プリプロセッサが理解できる定数が必要な場合は、プリプロセッサを使用して定義する必要があります。通常のC ++コードの定数が必要な場合は、通常のC ++コードを使用してください。

上記の例は、プリプロセッサの状態を示すためのものですが、そのコードでもプリプロセッサの使用を回避できます。

using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;

3
constexprそのアドレス(ポインタ/参照)が行われるまでメモリを占有しない可変必要。そうでなければ、完全に最適化することができます(そしてそれを保証するStandardeseがあるかもしれません)。これを強調したいのは、ストレージを必要としないenum些細なconstexprことでも一部を占めるという誤った考えから、人々が古い劣った「ハック」を使い続けないようにするためです。
underscore_d

3
「実際のメモリ位置」セクションが間違っています。1。値(int)で返されているため、コピーが作成されます。一時的なものは問題ありません。2.参照(int&)で戻ったint height場合、そのスコープは関数に関連付けられており、本質的に一時的であるため、マクロと同じように問題になります。3.上記のコメント「constint&hは一時的なものの寿命を延ばします」は正しいです。
PoweredByRice 2017

4
@underscore_d trueですが、引数は変わりません。変数は、odr-useがない限り、ストレージを必要としません。重要なのは、ストレージを備えた実変数が必要な場合、constexpr変数が正しいことを行うということです。
Jonathan Wakely 2017

1
@PoweredByRice 1.問題は、の戻り値とは何の関係もありません。limit問題はの戻り値ですstd::max。2.はい、それが参照を返さない理由です。3.間違っています。上記のcoliruリンクを参照してください。
Jonathan Wakely 2017

3
@PoweredByRiceため息、C ++がどのように機能するかを説明する必要はありません。あなたが持っていてconst int& h = max(x, y);maxその値で返す場合、その戻り値の寿命は延長されます。戻り値の型ではなく、const int&それにバインドされています。私が書いたことは正しいです。
Jonathan Wakely 2017年

11

一般的に言って、constexprマクロは他の解決策が不可能な場合にのみ使用する必要があります。

理論的根拠:

マクロはコード内の単純な置換であり、このため、多くの場合、競合が発生します(たとえば、windows.hmaxマクロとstd::max)。さらに、機能するマクロは別の方法で簡単に使用でき、奇妙なコンパイルエラーを引き起こす可能性があります。(例:Q_PROPERTY構造体メンバーで使用)

これらすべての不確実性のため、通常のgotoを回避するのとまったく同じように、マクロを回避することは適切なコードスタイルです。

constexpr は意味的に定義されているため、通常、発生する問題ははるかに少なくなります。


1
やむを得ずマクロを使用するのはどのような場合ですか?
トムドローネ2017

3
#ifつまり、プリプロセッサが実際に役立つものを使用した条件付きコンパイル。定数の定義は、プリプロセッサ条件で使用されるため、その定数がマクロである必要がある場合を除いて、プリプロセッサが役立つものの1つではありません#if。定数が通常のC ++コード(プリプロセッサディレクティブではない)で使用する場合は、プリプロセッサマクロではなく通常のC ++変数を使用します。
Jonathan Wakely 2017

可変個引数マクロを使用することを除いて、ほとんどはコンパイラスイッチにマクロを使用しますが、実際のコードステートメントを処理する現在のマクロステートメント(条件付き、文字列リテラルスイッチなど)をconstexprに置き換えようとするのは良い考えですか?

コンパイラスイッチも良い考えではないと思います。ただし、特にクロスプラットフォームまたは埋め込みコードを扱う場合は、それが必要になる場合があること(マクロも)を完全に理解しています。あなたの質問に答えるために:あなたがすでにプリプロセッサを扱っているなら、私はマクロを使ってプリプロセッサとは何か、そしてコンパイル時間とは何かを明確で直感的に保つでしょう。また、コメントを多くして、使用法をできるだけ短くローカルにすることをお勧めします(マクロが約100行または100行に広がるのを避けてください)。おそらく例外は、よく理解されている典型的な#ifndefガード(#pragmaの標準)です。
エイドリアンメア2017年

3

JonathonWakelyによる素晴らしい答え。また、私は見てとることをアドバイスしたいjogojapanの回答差が間にあるものになどconstconstexpr、あなたもマクロの使用を考慮して行く前に。

マクロはばかげていますが、良い意味で。表面上、最近では、特定のビルドパラメータが「定義」されている場合にのみ、コードの非常に特定の部分をコンパイルする場合のビルドエイドです。通常は、すべてその手段マクロの名前を取っている、またはより良いまだ、のは、それを呼び出してみましょうTrigger、と、のようなものの追加/D:Trigger-DTrigger使用されているビルドツールになど、。

マクロにはさまざまな用途がありますが、私が最もよく目にするのは、悪い/時代遅れの慣行ではない2つです。

  1. ハードウェアおよびプラットフォーム固有のコードセクション
  2. 冗長性の向上

したがって、OPの場合、intをconstexprまたはで定義するという同じ目標を達成できますが、MACRO最新の規則を使用する場合、2つが重複する可能性はほとんどありません。まだ段階的に廃止されていない一般的なマクロの使用法を次に示します。

#if defined VERBOSE || defined DEBUG || defined MSG_ALL
    // Verbose message-handling code here
#endif

マクロ使用の別の例として、リリースする予定のハードウェアがある場合、または他の人が必要としないいくつかのトリッキーな回避策がある特定の世代があるとします。このマクロをとして定義しますGEN_3_HW

#if defined GEN_3_HW && defined _WIN64
    // Windows-only special handling for 64-bit upcoming hardware
#elif defined GEN_3_HW && defined __APPLE__
    // Special handling for macs on the new hardware
#elif !defined _WIN32 && !defined __linux__ && !defined __APPLE__ && !defined __ANDROID__ && !defined __unix__ && !defined __arm__
    // Greetings, Outlander! ;)
#else
    // Generic handling
#endif
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.