62

何らかの理由でマクロを作成する必要があるとしましょうMACRO(X,Y)(インライン関数を使用できない正当な理由があると仮定しましょう。) このマクロで、戻り値のない関数の呼び出しをエミュレートする必要があります。


例1:これは期待どおりに機能するはずです。

if (x > y)
  MACRO(x, y);
do_something();

例2:これによってコンパイラエラーが発生することはありません。

if (x > y)
  MACRO(x, y);
else
  MACRO(y - x, x - y);

例3:これはコンパイルされるべきではありません。

do_something();
MACRO(x, y)
do_something();

マクロを書くための素朴な方法は次のようなものです。

#define MACRO(X,Y)                       \
cout << "1st arg is:" << (X) << endl;    \
cout << "2nd arg is:" << (Y) << endl;    \
cout << "Sum is:" << ((X)+(Y)) << endl;

これは3つの例すべてに失敗する非常に悪い解決策であり、その理由を説明する必要はありません。

マクロが実際に何をするかは無視してください。それは重要ではありません。


さて、私が最もよく書くマクロを見る方法は、次のように中かっこで囲むことです。

#define MACRO(X,Y)                         \
{                                          \
  cout << "1st arg is:" << (X) << endl;    \
  cout << "2nd arg is:" << (Y) << endl;    \
  cout << "Sum is:" << ((X)+(Y)) << endl;  \
}

これは、マクロが1つのステートメントブロックにあるため、例1を解決します。ただし、マクロの呼び出しの後にセミコロンを付けるため、例2は壊れています。これにより、コンパイラはセミコロン自体がステートメントであると見なします。つまり、elseステートメントはifステートメントに対応しません。最後に、例3は、セミコロンがない場合でも、コードブロックにセミコロンが必要ないため、正常にコンパイルされます。


3つの例すべてに合格するようにマクロを作成する方法はありますか?


注:私はヒントを共有するための受け入れられた方法の一部として自分の回答を提出していますが、誰かがより良い解決策を持っている場合は、ここに投稿してください。私の方法よりも多くの票を獲得する可能性があります。:)

4

9 に答える 9

50

かなり賢い解決策があります:

#define MACRO(X,Y)                         \
do {                                       \
  cout << "1st arg is:" << (X) << endl;    \
  cout << "2nd arg is:" << (Y) << endl;    \
  cout << "Sum is:" << ((X)+(Y)) << endl;  \
} while (0)

これで、単一のブロックレベルのステートメントが作成され、その後にセミコロンを付ける必要があります。これは、3つの例すべてで期待どおりに動作します。

于 2008-10-02T16:41:21.847 に答える
50

通常、マクロは避ける必要があります。常にインライン関数を優先します。その塩に値するコンパイラは、小さな関数をマクロであるかのようにインライン化できる必要があります。インライン関数は、名前空間やその他のスコープを尊重し、すべての引数を一度評価します。

マクロでなければならない場合は、whileループ(すでに提案されています)が機能するか、コンマ演算子を試すことができます。

#define MACRO(X,Y) \
 ( \
  (cout << "1st arg is:" << (X) << endl), \
  (cout << "2nd arg is:" << (Y) << endl), \
  (cout << "3rd arg is:" << ((X) + (Y)) << endl), \
  (void)0 \
 )

これ(void)0により、ステートメントはvoidタイプの1つに評価され、セミコロンではなくコンマを使用すると、スタンドアロンとしてだけでなく、ステートメント内で使用できるようになります。私はまだ多くの理由でインライン関数をお勧めしますが、その中で最も少ないのはスコープと2回MACRO(a++, b++)インクリメントするという事実です。ab

于 2008-10-02T16:54:59.677 に答える
20

「マクロの機能を無視する」と言ったのは知っていますが、タイトルに基づいて検索するとこの質問が見つかるので、マクロで関数をエミュレートするためのさらなる手法についての議論が必要だと思います.

私が知っている最も近いものは次のとおりです。

#define MACRO(X,Y) \
do { \
    auto MACRO_tmp_1 = (X); \
    auto MACRO_tmp_2 = (Y); \
    using std::cout; \
    using std::endl; \
    cout << "1st arg is:" << (MACRO_tmp_1) << endl;    \
    cout << "2nd arg is:" << (MACRO_tmp_2) << endl;    \
    cout << "Sum is:" << (MACRO_tmp_1 + MACRO_tmp_2) << endl; \
} while(0)

これにより、次のことが行われます。

  • 記載されている各コンテキストで正しく機能します。
  • 各引数を正確に 1 回評価します。これは、関数呼び出しの保証された機能です (どちらの場合も、これらの式のいずれにも例外がないと仮定します)。
  • C++0x の「auto」を使用して、任意の型に作用します。これはまだ標準の C++ ではありませんが、単一評価規則によって必要とされる tmp 変数を取得する方法は他にありません。
  • 呼び出し元が名前空間 std から名前をインポートする必要はありません。これは元のマクロでは必要ですが、関数では必要ありません。

ただし、次の点で関数とは異なります。

  • 一部の無効な使用法では、さまざまなコンパイラ エラーまたは警告が発生する場合があります。
  • X または Y に周囲のスコープからの 'MACRO_tmp_1' または 'MACRO_tmp_2' の使用が含まれていると、問題が発生します。
  • 名前空間の std に関連します。関数は独自の字句コンテキストを使用して名前を検索しますが、マクロは呼び出しサイトのコンテキストを使用します。この点で関数のように振る舞うマクロを書く方法はありません。
  • void 式 (コンマ ソリューションなど) が使用できる void 関数の戻り式として使用することはできません。これは、目的の戻り値の型が void でない場合、特に左辺値として使用される場合に、さらに問題になります。ただし、コンマ ソリューションには using 宣言を含めることはできません。これはステートメントであるため、いずれかを選択するか、({ ... }) GNU 拡張機能を使用してください。
于 2008-10-02T23:26:25.540 に答える
17

libc6これが!からの答えです。を見て/usr/include/x86_64-linux-gnu/bits/byteswap.h、あなたが探していたトリックを見つけました。

以前のソリューションに対するいくつかの批評家:

  • Kip の解決策では、最終的に必要になることが多い式への評価が許可されていません。
  • coppro のソリューションでは、式が別々であるため、変数を割り当てることはできませんが、式を評価することはできます。
  • Steve Jessop のソリューションでは C++11autoキーワードを使用していますが、それで問題ありませんが、代わりに既知の/期待される型を自由に使用してください。

トリックは、(expr,expr)コンストラクトと{}スコープの両方を使用することです。

#define MACRO(X,Y) \
  ( \
    { \
      register int __x = static_cast<int>(X), __y = static_cast<int>(Y); \
      std::cout << "1st arg is:" << __x << std::endl; \
      std::cout << "2nd arg is:" << __y << std::endl; \
      std::cout << "Sum is:" << (__x + __y) << std::endl; \
      __x + __y; \
    } \
  )

キーワードの使用に注意してくださいregister。これは、コンパイラへのヒントにすぎません。およびマクロ パラメータは (既に) 括弧で囲まれており、期待される型にXキャストされています。このソリューションは、パラメーターが 1 回だけ評価されるため、プレインクリメントとポストインクリメントで適切に機能します。Y

例として、要求されていませんが、__x + __y;ステートメントを追加しました。これは、ブロック全体をその正確な式として評価する方法です。

void();マクロが式に評価されないことを確認したい場合に使用する方が安全rvalueです。

ただし、ソリューションはISO C++ に準拠していませんg++ -pedantic

warning: ISO C++ forbids braced-groups within expressions [-pedantic]

に休息を与えるためにg++、 を使用(__extension__ OLD_WHOLE_MACRO_CONTENT_HERE)して、新しい定義が次のようになるようにします。

#define MACRO(X,Y) \
  (__extension__ ( \
    { \
      register int __x = static_cast<int>(X), __y = static_cast<int>(Y); \
      std::cout << "1st arg is:" << __x << std::endl; \
      std::cout << "2nd arg is:" << __y << std::endl; \
      std::cout << "Sum is:" << (__x + __y) << std::endl; \
      __x + __y; \
    } \
  ))

私のソリューションをさらに改善するために、 C の MIN および MAX に__typeof__見られるように、キーワードを使用しましょう。

#define MACRO(X,Y) \
  (__extension__ ( \
    { \
      __typeof__(X) __x = (X); \
      __typeof__(Y) __y = (Y); \
      std::cout << "1st arg is:" << __x << std::endl; \
      std::cout << "2nd arg is:" << __y << std::endl; \
      std::cout << "Sum is:" << (__x + __y) << std::endl; \
      __x + __y; \
    } \
  ))

これで、コンパイラは適切な型を決定します。これもgcc延長です。

registerクラス型で使用すると次の警告が表示されるため、キーワードの削除に注意してください。

warning: address requested for ‘__x’, which is declared ‘register’ [-Wextra]
于 2011-12-21T13:36:58.787 に答える
4

を使用してブロックを作成します

 #define MACRO(...) do { ... } while(false)

;を追加しないでください。while(false)の後

于 2008-10-02T16:50:39.500 に答える
3

あなたの答えは複数評価の問題に悩まされているので、(eg)

macro( read_int(file1), read_int(file2) );

予想外の、おそらく望ましくないことをします。

于 2008-10-02T17:11:24.897 に答える
1

他の人が述べているように、可能な限りマクロは避けるべきです。マクロ引数が複数回評価される場合、副作用が存在する場合は危険です。引数のタイプがわかっている場合(またはC ++ 0xauto機能を使用できる場合)、一時的なものを使用して単一の評価を実施できます。

別の問題:複数の評価が行われる順序は、期待したものではない可能性があります。

このコードを考えてみましょう:

#include <iostream>
using namespace std;

int foo( int & i ) { return i *= 10; }
int bar( int & i ) { return i *= 100; }

#define BADMACRO( X, Y ) do { \
    cout << "X=" << (X) << ", Y=" << (Y) << ", X+Y=" << ((X)+(Y)) << endl; \
    } while (0)

#define MACRO( X, Y ) do { \
    int x = X; int y = Y; \
    cout << "X=" << x << ", Y=" << y << ", X+Y=" << ( x + y ) << endl; \
    } while (0)

int main() {
    int a = 1; int b = 1;
    BADMACRO( foo(a), bar(b) );
    a = 1; b = 1;
    MACRO( foo(a), bar(b) );
    return 0;
}

そして、それはコンパイルされて私のマシンで実行されるように出力されます:

X = 100、Y = 10000、X + Y = 110
X = 10、Y = 100、X + Y = 110
于 2008-10-02T17:46:00.303 に答える
0

ifステートメントで常に中括弧を使用する方法を採用する場合は、

マクロには、最後のセミコロンがないだけです。

#define MACRO(X,Y)                       \
cout << "1st arg is:" << (X) << endl;    \
cout << "2nd arg is:" << (Y) << endl;    \
cout << "Sum is:" << ((X)+(Y)) << endl

例1:(コンパイル)

if (x > y) {
    MACRO(x, y);
}
do_something();

例2:(コンパイル)

if (x > y) {
    MACRO(x, y);
} else {
    MACRO(y - x, x - y);
}

例3:(コンパイルしない)

do_something();
MACRO(x, y)
do_something();
于 2008-10-02T16:54:33.143 に答える