従来のコードと最新のコードでは、マクロを使用してコード生成などの気の利いたソリューションを実行します。また、演算子#
と演算子の両方を使用し##
ます。
他の開発者がマクロを使用してクールなことを行う方法に興味があります。
従来のコードと最新のコードでは、マクロを使用してコード生成などの気の利いたソリューションを実行します。また、演算子#
と演算子の両方を使用し##
ます。
他の開発者がマクロを使用してクールなことを行う方法に興味があります。
Cでは、逐語的な引数を取得するためのマクロを定義すると同時に、そのアドレスを透過的に取得できるように関数を定義するのが一般的です。
// could evaluate at compile time if __builtin_sin gets
// special treatment by the compiler
#define sin(x) __builtin_sin(x)
// parentheses avoid substitution by the macro
double (sin)(double arg) {
return sin(arg); // uses the macro
}
int main() {
// uses the macro
printf("%f\n", sin(3.14));
// uses the function
double (*x)(double) = &sin;
// uses the function
printf("%f\n", (sin)(3.14));
}
DRYや単純なコード生成に役立つXマクロイディオムもあります。
まだ定義されていないマクロを使用して、ヘッダーgen.xaの種類のテーブルで定義します。
/** 1st arg is type , 2nd is field name , 3rd is initial value , 4th is help */
GENX( int , "y" , 1 , "number of ..." );
GENX( float , "z" , 6.3 , "this value sets ..." );
GENX( std::string , "name" , "myname" , "name of ..." );
次に、通常は異なる定義で#includeごとに定義するさまざまな場所で使用できます。
class X
{
public :
void setDefaults()
{
#define GENX( type , member , value , help )\
member = value ;
#include "gen.x"
#undef GENX
}
void help( std::ostream & o )
{
#define GENX( type , member , value , help )\
o << #member << " : " << help << '\n' ;
#include "gen.x"
#undef GENX
}
private :
#define GENX( type , member , value , help )\
type member ;
#include "gen.x"
#undef GENX
}
最もクールなマクロは、アサート、ガードのインクルード、__FILE__、__LINE__ です。
コードで他のマクロを使用しないでください。
編集:
マクロは、法的な解決策がない場合にのみ使用してください。
デバッグ用のSHOW():
#define SHOW(X) cout << # X " = " << (X) << endl
引数を拡張するための二重評価のトリック:(たとえば、「__ LINE__」ではなく実際の行番号を使用します。)
/* Use CONCATENATE_AGAIN to expand the arguments to CONCATENATE */
#define CONCATENATE( x,y) CONCATENATE_AGAIN(x,y)
#define CONCATENATE_AGAIN(x,y) x ## y
静的コンパイル時アサーション。
例えば:
#define CONCATENATE_4( a,b,c,d) CONCATENATE_4_AGAIN(a,b,c,d)
#define CONCATENATE_4_AGAIN(a,b,c,d) a ## b ## c ## d
/* Creates a typedef that's legal/illegal depending on EXPRESSION. *
* Note that IDENTIFIER_TEXT is limited to "[a-zA-Z0-9_]*". *
* (This may be replaced by static_assert() in future revisions of C++.) */
#define STATIC_ASSERT( EXPRESSION, IDENTIFIER_TEXT) \
typedef char CONCATENATE_4( static_assert____, IDENTIFIER_TEXT, \
____failed_at_line____, __LINE__ ) \
[ (EXPRESSION) ? 1 : -1 ]
経由で使用:
typedef int32_t int4;
STATIC_ASSERT( sizeof(int4) == 4, sizeof_int4_equal_4 );
クラスCodeLocationのインスタンスの初期化:(呼び出しのポイントからのファイル/ライン/関数の保存-これは、マクロを使用するか、ソースポイントの__FILE __ / __ LINE __ / etcマクロに直接アクセスすることによって*のみ*実行できます。)
/* Note: Windows may have __FUNCTION__. C99 defines __func__. */
#define CURRENT_CODE_LOCATION() \
CodeLocation( __PRETTY_FUNCTION__, __FILE__, __LINE__ )
その後、便利なソースロケーション印刷メカニズムとしてMESSAGE / WARN/FAILマクロによって使用されます。例えば:
#define WARN_IF_NAN(X) \
do \
{ \
if ( isnan(X) != 0 ) \
WARN( # X " is NaN (Floating Point NOT-A-NUMBER)" ); \
if ( isinf(X) != 0 ) \
WARN( # X " is INF (Floating Point INFINITY)" ); \
} while ( false )
マクロをアサート/アンレスします。'=='のような演算子を含む、任意のトークンをマクロを介して渡すことができます。したがって、次のように構成されます。
ASSERT( foo, ==, bar )
または
UNLESS( foo, >=, 0, value=0; return false; );
合法です。アサート/マクロがCodeLocation、スタックトレース、例外のスロー/コアダンプ/正常終了などの便利な情報をすべて自動的に追加できる場合を除きます。
errnoをより単純にする:
#define ERRNO_FORMAT "errno= %d (\"%s\")"
#define ERRNO_ARGS errno, strerror(errno)
#define ERRNO_STREAM "errno= " << errno << " (\"" << strerror(errno) << "\") "
例:printf("オープンに失敗しました。"ERRNO_FORMAT、ERRNO_ARGS);
Boost.Preprocessorを見て、プリプロセッサの興味深い用途をたくさん見つけることができます...
私はショーン・バレットがこの楽しいものを作ったと信じています:
#ifndef blah
#define blah(x) // something fun
#include __FILE__
#undef blah
#endif
#ifndef blah
#define blah(x) // something else that is also fun
#include __FILE__
#undef blah
#endif
#ifdef blah
blah(foo)
blah(bar)
#endif
マクロを介して表現できる高レベルの構造に基づいて、プリプロセッサにコードを生成させるハックな方法。
私のお気に入りのトリックの 1 つは、可変数の引数をマクロに渡す方法です。これは、後でたとえば printf のような関数を呼び出すときに使用されます。これを行うには、マクロにパラメーターが 1 つしかないことを指定し、() なしでマクロの本体で使用しますが、すべてのパラメーターを (( および )) でマクロに渡すため、リストは単一の引数のように見えます。例えば、
#define TRACE( allargs) do { printf allargs; } while ( 0)
...
TRACE(( "%s %s\n", "Help", "me"));
ロギングは、マクロが特に頻繁に使用される場所の1つです。
#define LOG(log) \
if (!log.enabled()) {} \
else log.getStream() << __FILE__ << "@" << __LINE__ << ": "
log_t errorlog;
...
LOG(errorlog) << "This doesn't look good:" << somedata;
組み込みコードの場合、 embeddedgurus.comの優れたトリック により、バイナリ値を処理できます。
B8(01010101) // 85
B16(10101010,01010101) // 43,605
B32(10000000,11111111,10101010,01010101) // 2,164,238,93
これは、少し拡張されていますが、BOOST_BINARY に関する @Ferruccio からの以前の応答と同様の目標を達成します。
コードは次のとおりです(コピーして貼り付け、テストしていません。詳細についてはリンクを参照してください)
// Internal Macros
#define HEX__(n) 0x##n##LU
#define B8__(x) ((x&0x0000000FLU)?1:0) \
+((x&0x000000F0LU)?2:0) \
+((x&0x00000F00LU)?4:0) \
+((x&0x0000F000LU)?8:0) \
+((x&0x000F0000LU)?16:0) \
+((x&0x00F00000LU)?32:0) \
+((x&0x0F000000LU)?64:0) \
+((x&0xF0000000LU)?128:0)
// User-visible Macros
#define B8(d) ((unsigned char)B8__(HEX__(d)))
#define B16(dmsb,dlsb) (((unsigned short)B8(dmsb)<<8) + B8(dlsb))
#define B32(dmsb,db2,db3,dlsb) \
(((unsigned long)B8(dmsb)<<24) \
+ ((unsigned long)B8(db2)<<16) \
+ ((unsigned long)B8(db3)<<8) \
+ B8(dlsb))
マクロが好きです。デバッグするときはとても楽しいです!
私がマクロを使用する主な場所は、私自身のテスト フレームワークです。たとえば、一部のコードがスローする必要があることをアサートしたい場合は、次のマクロを使用します。
#define MUST_THROW( expr )
try {
(expr);
(myth_suite_).Fail( #expr +
std::string( " should throw but didn't" ) );
}
catch( ... ) {
}
そして、次のように使用します。
MUST_THROW( some_bogus_stuff() );
MUST_THROW( more_bogus_stuff() );
私がそれらを使用する他の唯一の場所は、クラス宣言です。私はマクロを持っています:
#define CANNOT_COPY( cls ) \
private: \
cls( const cls & ); \
void operator=( const cls & ) \
クラスをコピー (または割り当て) できないことを指定するために使用します。
class BankAccount {
CANNOT_COPY( BankAccount );
....
};
これは特別なことは何もしませんが、人々の注意を引き、簡単に検索できます。
C99 可変個引数マクロを使用した、デフォルト値 (ゼロではない) を持つ構造体リテラル
struct Example {
int from;
int to;
const char *name;
}
#define EXAMPLE(...) ((struct Example){.from=0, .to=INT_MAX, .name="", __VA_ARGS__})
usingEXAMPLE(.name="test")
は、 の明示的なオーバーライドを除いて、デフォルト値を使用しますname
。同じメンバーの後で言及するこのシャドーイングは、標準で明確に定義されています。
私はよく、デバッグ ソナーのようなものを単純なマクロでラップして、リリース ビルドからコンパイルできるようにします。
#ifdef DEBUG
#define D(s) do { s; } while(0)
#else
#define D(s) do {/**/} while(0)
#endif
後で使用するのは、通常、次のようなものです。
D(printf("level %d, condition %s\n", level, condition));
このイディオムは、条件付きまたはループの唯一のコンテンツをdo{}while(0)
誤って使用することによって生じる可能性のある問題を回避するためにあります。D(...)
結局のところ、このようなコードが間違ったことを意味することは望ましくありません。
for(i=1;i<10;++i) D(printf("x[%d]=%f\n",i,x[i]));
SomeReallyExpensiveFunction(x);
その場合にエラーをスローさせることができればそうしますが、D()
マクロがループ本体の唯一の内容であることを伝えるには、プリプロセッサ自体が完全なコンパイラである必要があります。
私はまた、コンパイル時のアサーションの大ファンでもあります。私の処方は少し異なりますが、私が見た他のものよりも実際の利点はありません. 重要なのは、アサートされた条件が false の場合にエラーをスローし、それ以外の場合にはスローしない一意の名前の typedef を形成することです。cassert.h には次のものがあります。
/*! \brief Compile-time assertion.
*
* Note that the cassert() macro generates no code, and hence need not
* be restricted to debug builds. It does have the side-effect of
* declaring a type name with typedef. For this reason, a unique
* number or string of legal identifier characters must be included
* with each invocation to avoid the attempt to redeclare a type.
*
* A failed assertion will attempt to define a type that is an array
* of -1 integers, which will throw an error in any standards
* compliant compiler. The exact error is implementation defined, but
* since the defined type name includes the string "ASSERTION" it
* should trigger curiosity enough to lead the user to the assertion
* itself.
*
* Because a typedef is used, cassert() may be used inside a function,
* class or struct definition as well as at file scope.
*/
#define cassert(x,i) typedef int ASSERTION_##i[(x)?1:-1]
また、一部のソース ファイルでは、typedef が有効な場所はどこでも次のようになります。
#include "cassert.h"
...
cassert(sizeof(struct foo)==14, foo1);
...
結果として生じるエラー メッセージは、多くの場合あいまいですが、問題のある行を力ずくで発見できるようにする識別子の断片が含まれます。
列挙型メンバーの名前の一意の部分に基づいて多くのボイラープレートを生成した別の回答のコードのように、コード生成ユーティリティを作成することが好ましい回答であった可能性がある場所でプリプロセッサを使用したことで罪を犯しました。これは、C でコンパイルするメッセージ ディスパッチ グルーを大量に作成する場合に特に便利です。
つまり、反復的なことを単純化できます。列挙リスト
enum {
kOneEnum,
kTwoEnum,
kThreeEnum,
kFourEnum
};
...そして後で構造化された方法でスイッチケースを実行します
#define TEST( _v ) \
case k ## _v ## Enum: \
CallFunction ## _v(); \
break;
switch (c) {
TEST( One );
TEST( Two );
TEST( Three );
TEST( Four );
}
注:確かにこれは関数ポインター配列で実行できますが、これにより、パラメーターを追加したり、単一のハッシュで文字列展開を使用したりするための柔軟性が少し広がります。
...または文字列をテストして正しい列挙値を取得する
int value = -1;
char *str = getstr();
#define TEST( _v ) \
if (!strcmp(# _v, str)) \
value = k ## _v ## Enum
TEST( One );
TEST( Two );
TEST( Three );
TEST( Four );
マクロを使用して、異なるデータ型で同じ機能を定義できます。例えば:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#define DEFINE_BITS_STR(name, type) \
char *bits_str_##name(type value) \
{ \
int len = sizeof(type) * CHAR_BIT; \
char *result; \
type n; \
int i; \
\
result = (char *)calloc(len+1, sizeof(type)); \
if(result == NULL) \
return NULL; \
\
memset(result, '0', len); \
result[len] = 0x00; \
\
n = value; \
i = len; \
while(n) \
{ \
if(n & 1) \
result[i] = '1'; \
\
n >>= 1; \
--i; \
} \
\
return result; \
}
DEFINE_BITS_STR(uchar, unsigned char)
DEFINE_BITS_STR(uint, unsigned int)
DEFINE_BITS_STR(int, unsigned int)
int main()
{
unsigned char value1 = 134;
unsigned int value2 = 232899;
int value3 = 255;
char *ret;
ret = bits_str_uchar(value1);
printf("%d: %s\n", value1, ret);
ret = bits_str_uint(value2);
printf("%d: %s\n", value2, ret);
ret = bits_str_int(value3);
printf("%d: %s\n", value3, ret);
return 1;
}
この例では、 3 つの異なるデータ型 ( 、、 ) を処理する 3 つの関数 ( bits_str_uchar()
、bits_str_uint()
、 ) を定義します。ただし、すべて渡された値のビットを含む文字列を返します。bits_str_int()
unsigned char
unsigned int
int
CrashRpt プロジェクトから、マクロを拡張して定義するためのトリックが必要です。
#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);
COM サーバーを実装するときは、コードがスローする可能性のあるすべての例外に注意する必要があります。COM メソッドの境界を介して例外を許可すると、呼び出し元のアプリケーションがクラッシュすることがよくあります。
メソッドブラケットはこれに役立ちます。「try」を含むマクロである開き括弧と、一連の「catch」を含む閉じ括弧があり、例外を ErrorInfo にラップして HRESULT を生成します。
ハードウェア ブレークポイントには多くの欠点があるため、マイクロ コントローラーでは UART を使用してコードをデバッグするのが一般的です。
これは、非常に便利であることが証明されている単純なマクロです。
#define DEBUG_OUT(value) sprintf(uartTxBuf, "%s = 0x%04X\n", #value, value);\
puts_UART((uint16_t *) uartTxBuf)
使用例:
for (i=0; i < 4; i++)
{
DEBUG_OUT(i);
DEBUG_OUT(i % 3);
}
受信したストリーム:
i = 0x0000
i % 3 = 0x0000
i = 0x0001
i % 3 = 0x0001
i = 0x0002
i % 3 = 0x0002
i = 0x0003
i % 3 = 0x0000
はい、それは粗雑で安全ではありません。バグが分離されるまでのみ適用されるため、このマクロは害を及ぼすことはありません。
ほとんどの (すべて?) C++ ユニット テスト フレームワークは、マクロに基づいて構築されています。UnitTest++を使用します。あらゆる種類の派手なマクロを確認するには、それをチェックしてください。
BOOST_BINARYマクロは、clevel プリプロセッサのトリッキーを実行して、数値定数をバイナリで表現する機能を C++ に提供します。ただし、0 ~ 255 に制限されています。
よくこれを使います。私はdebug.h
次のように定義されたヘッダーを持っています:
#ifndef DEBUG_H
#define DEBUG_H
#ifdef DEBUG
#define debuf if(1)
#else
#define debug if(0)
#endif
#endif
その後:
debug {
printf("message from debug!");
}
メッセージを取得したい場合は、次"message from debug!"
のコマンドでコンパイルします。
gcc -D DEBUG foo.c
そうでなければ、何も起こりません。Gccは非常にスマートなコンパイラです。DEBUG
が定義されていない場合、生成されたif(0)
(デッドコード)は、いくつかの最適化をオンにしてコードから削除されます。
あなたはまだもっとすることができます:
debug
{
pritnf("I'm in debug mode!\n");
}
else
{
printf("I'm not in debug mode\n");
}
数日前、私はDプログラミング言語が非常によく似た機能を提供するのを見ました。
上記を文脈なしで考える場合、thinksを次のように定義できます。
#define in_debug if(1)
#define not_debug else
その後
in_debug {
printf("I'm in debug mode!");
}
not_debug {
printf("Not in debug mode!");
}
それらを言語の構成に変換して、型の安全性とデバッグ能力を向上させます。
void _zero_or_die(int v, const char* filename, int line)
{
if (v != 0)
{
fprintf(stderr, "error %s:%d\n", filename, line);
exit(1);
}
}
#define ZERO_OR_DIE_ for (int _i=1; _i == 1; _zero_or_die(_i, __FILE__, __LINE__)) _i=
ZERO_OR_DIE_ pipe(fd);
ZERO_OR_DIE_ close(0);
ZERO_OR_DIE_ sigaction(SIGSEGV, &sigact, NULL);
ZERO_OR_DIE_ pthread_mutex_lock(&mt);
ZERO_OR_DIE_ pthread_create(&pt, NULL, func, NULL);
pthreads ユーティリティ マクロは特に印象的です。
3GPP RRC/NBAP/RNSAP に使用される構造のような巨大な c/c++ ネストされた構造で作業する場合、コードをきれいに見せるためにこのトリックに従います。
struct leve1_1
{
int data;
struct level2
{
int data;
struct level3
{
int data;
} level_3_data;
} level_2_data;
} level_1_data;
level_1_data.data = 100;
#define LEVEL_2 leve1_1_data.level_2_data
LEVEL_2.data = 200;
#define LEVEL_3 LEVEL_2.level_3_data
LEVEL_3.data = 300;
#undef LEVEL_2
#undef LEVEL_3
これにより、メンテナンス時間中の生活が楽になります..設計時間でもコードが読みやすくなります.