macrosの使用を好む場所とconstexprの好む場所それらは基本的に同じではありませんか?
#define MAX_HEIGHT 720
対
constexpr unsigned int max_height = 720;
それらは基本的に同じではありませんか?
いいえ、まったく違います。程遠い。
マクロがint
であり、constexpr unsigned
がunsigned
であるという事実とは別に、重要な違いがあり、マクロにはoneの利点しかありません。
マクロはプリプロセッサによって定義され、発生するたびにコードに単純に置き換えられます。プリプロセッサはdumbであり、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
プリプロセッサは変数を名前で参照する方法を理解していないため、ここでは変数を使用できません。マクロ展開や#
で始まるディレクティブ(#include
、#define
、#if
など)などの基本的な非常に基本的なことのみを理解します。
プリプロセッサが理解できる定数が必要な場合、プリプロセッサを使用して定義する必要があります。通常のC++コードの定数が必要な場合は、通常のC++コードを使用します。
上記の例は単にプリプロセッサの状態を示すためのものですが、そのコードでさえプリプロセッサの使用を回避できます。
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
一般的に言って、constexpr
はいつでも使用でき、マクロは他の解決策がない場合にのみ使用する必要があります。
マクロはコード内の単純な置換であり、このため、しばしば競合を生成します(例:windows.h max
マクロとstd::max
)。さらに、動作するマクロを簡単に別の方法で使用すると、奇妙なコンパイルエラーが発生する可能性があります。 (例:Q_PROPERTY
構造体メンバーで使用)
これらのすべての不確実性のため、通常gotoを避けるのとまったく同じように、マクロを避けるのは良いコードスタイルです。
constexpr
はセマンティックに定義されているため、通常、生成される問題ははるかに少なくなります。
Jonathon Wakely による素晴らしい回答。また、マクロの使用を検討する前に、const
とconstexpr
の違いについて jogojapanの答え を確認することをお勧めします。
マクロは愚かですが、良い方法です。表面的には、特定のビルドパラメーターが "定義"されている場合にのみ、コードの非常に特定の部分をコンパイルする場合のビルドエイドです。通常、すべての意味は、マクロ名を取得すること、またはさらに良いことに、それをTrigger
と呼び、使用中のビルドツールに/D:Trigger
、-DTrigger
などを追加します。 。
マクロには多くの異なる用途がありますが、これらは私が最もよく見かける、悪い/時代遅れのプラクティスではない2つです:
したがって、OPの場合、constexpr
またはMACRO
を使用してintを定義するという同じ目標を達成できますが、現代の規則を使用するときに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