Frage

Lassen Sie uns sagen, dass aus irgendeinem Grund müssen Sie ein Makro schreiben: MACRO(X,Y). (Nehmen wir an, es gibt einen guten Grund, warum Sie nicht eine Inline-Funktion verwenden können.) Sie möchten dieses Makro einen Aufruf einer Funktion ohne Rückgabewert zu emulieren.


Beispiel 1:. Dies sollte wie erwartet

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

Beispiel 2:. Dies sollte nicht in einem Compiler-Fehler führen

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

Beispiel 3:. Dies sollte nicht kompiliert

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

Die naive Art und Weise das Makro ist wie folgt zu schreiben:

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

Dies ist eine sehr schlechte Lösung, die alle drei Beispiele fehlschlägt, und ich soll nicht erklären, warum muß.

Ignorieren Sie, was das Makro tatsächlich tut, das ist nicht der Punkt.


Nun wird die Art, wie ich am häufigsten Makros geschrieben sehen ist sie in geschweiften Klammern zu umschließen, wie folgt aus:

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

Das löst Beispiel 1, weil das Makro in einem Anweisungsblock ist. Aber Beispiel 2 ist gebrochen, weil wir ein Semikolon nach dem Aufruf an das Makro setzen. Dies macht den Compiler denkt, das Semikolon ist eine Aussage von selbst, was die sonst Aussage bedeutet nicht zu einem if-Anweisung entsprechen! Und schließlich Beispiel 3 kompiliert OK, auch wenn es kein Semikolon, weil ein Codeblock kein Semikolon benötigen.


Gibt es eine Möglichkeit, einen Makro zu schreiben, so dass es alle drei Beispiele passieren?


Hinweis: Ich bin meine eigene Antwort als Teil des akzeptierte Art und Weise einen Tipp geben, aber wenn jemand eine bessere Lösung hat das Gefühl, frei, um es hier zu schreiben, kann es mehr Stimmen als meine Methode erhalten. :)

War es hilfreich?

Lösung

Makros sollten generell vermieden werden; lieber Inline-Funktionen jederzeit zu ihnen. Jeder Compiler wert sein Salz sollte eine kleine Funktion von inlining der Lage sein, als ob es ein Makro war, und eine Inline-Funktion respektiert Namensräume und andere Bereiche, sowie die Würdigung aller Argumente einmal.

Wenn es muss ein Makro, eine while-Schleife (bereits vorgeschlagen) arbeiten, oder Sie können den Komma-Operator versuchen:

#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 \
 )

Die (void)0 bewirkt, dass die Anweisung zu einem der void Art zu bewerten, und die Verwendung von Kommas statt Semikolons erlaubt es in einer Anweisung verwendet werden, und nicht nur als eigenständiges. Ich würde immer noch eine Inline-Funktion für eine Vielzahl von Gründen, die am wenigsten von denen sein Umfang und die Tatsache, empfehlen, dass MACRO(a++, b++) erhöht wird a und b zweimal.

Andere Tipps

Es ist eine ziemlich clevere Lösung:

#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)

Jetzt haben Sie eine einzige Block-Level-Anweisung, die durch ein Semikolon gefolgt werden muss. Dies verhält sich wie in allen drei Beispielen erwartet und gewünscht wird.

Ich weiß, Sie sagte: „ignorieren, was das Makro tut“, aber die Menschen werden diese Frage finden, indem Sie auf den Titel basierte Suche, so dass ich denke, die Diskussion über weitere Techniken Funktionen zu emulieren mit Makros gerechtfertigt sind.

Closest Ich kenne lautet:

#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)

Dies geschieht Folgendes:

  • funktioniert einwandfrei in jeder der genannten Kontexte.
  • Wertet jedes seiner Argumente genau einmal, die eine zugesicherte Eigenschaft eines Funktionsaufrufs wird (in beiden Fällen keine Ausnahmen in einer dieser Ausdrücke vorausgesetzt).
  • Wirkt auf alle Arten, durch die Verwendung von "auto" von C ++ 0x. Dies ist noch nicht Standard C ++, aber es gibt keine andere Möglichkeit, die tmp Variablen durch die einzelne Auswertungsregel erforderlich zu erhalten.
  • Ist es erforderlich, den Anrufer nicht importiert Namen von Namespace std zu haben, die das ursprüngliche Makro tut, aber eine Funktion würde nicht.

Allerdings unterscheidet es sich nach wie vor von einer Funktion, dass:

  • In einigen ungültigen verwendet es unterschiedliche Compiler-Fehler oder Warnungen geben kann.
  • Es geht schief, wenn X oder Y enthält Verwendungen von ‚MACRO_tmp_1‘ oder ‚MACRO_tmp_2‘ aus dem umgebenden Rahmen.
  • Im Zusammenhang mit dem Namespace std Sache: eine Funktion seinen eigenen lexikalischen Kontext verwendet Namen nachschlagen, während ein Makro den Kontext seines Aufrufort verwendet. Es gibt keine Möglichkeit, einen Makro zu schreiben, das in dieser Hinsicht wie eine Funktion verhält.
  • Es kann nicht als die Rückkehr Ausdruck einer Leere Funktion, die ein Leerer Ausdruck (wie die Komma-Lösung) verwendet werden. Dies ist noch ein Problem, wenn der gewünschte Rückgabetyp nicht leer ist, vor allem, wenn sie als L-Wert verwendet. Aber die Komma-Lösung kann nicht enthalten Erklärungen zu verwenden, da sie Aussagen sind, so wählen Sie ein oder verwenden Sie die ({...}) GNU-Erweiterung.

Hier ist eine Antwort direkt aus dem libc6 kommen! Wirft man einen Blick auf /usr/include/x86_64-linux-gnu/bits/byteswap.h, fand ich den Trick, den Sie gesucht haben.

Einige Kritiker der bisherigen Lösungen:

  • Kip-Lösung erlaubt es nicht, Auswertung zu einem Ausdruck , die am Ende ist oft erforderlich.
  • coppro-Lösung erlaubt es nicht, eine Variable zuweisen wie die Ausdrücke sind getrennt, aber auf einen Ausdruck bewerten.
  • Steve Jessop-Lösung verwendet das C ++ 11 auto Schlüsselwort, das ist in Ordnung, aber fühlen Sie sich frei, die bekannten / erwarteten Typen , anstatt zu verwenden.

Der Trick ist, sowohl das (expr,expr) Konstrukt und einen {} Umfang zu nutzen:

#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; \
    } \
  )

Beachten Sie die Verwendung des register Schlüsselwort, es ist nur ein Hinweis an den Compiler. Die X und Y Makroparameter sind (bereits) umgeben in Klammern und gegossenem zu einem erwarteten Typ. Diese Lösung funktioniert mit Pre- und Post-Inkrement als Parameter nur einmal ausgewertet werden.

Für das Beispiel Zweck, auch wenn nicht angefordert, fügte ich die __x + __y; Aussage, welche die Art und Weise ist den ganzen Block zu machen, wie genau diesen Ausdruck ausgewertet werden.

Es ist sicherer void(); zu verwenden, wenn Sie das Makro, um sicherzustellen, wollen nicht auf einen Ausdruck auszuwerten, damit rechtswidrig sein, wo ein rvalue erwartet wird.

Doch , die Lösung ist nicht ISO C ++ konform als g++ -pedantic beschweren:

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

Um etwas Ruhe zu geben, um g++, verwenden (__extension__ OLD_WHOLE_MACRO_CONTENT_HERE) so dass die neue Definition lautet:

#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; \
    } \
  ))

Um meine Lösung zu verbessern, noch ein bisschen mehr, lassen Sie sich das __typeof__ Schlüsselwort verwenden, wie in MIN und MAX in C :

#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; \
    } \
  ))

Nun wird der Compiler den entsprechenden Typ bestimmen. Auch dies ist eine gcc Erweiterung.

Beachten Sie die Entfernung des register Schlüsselwort, wie es die folgende Warnung würde, wenn sie mit einem Klasse-Typ verwendet:

warning: address requested for ‘__x’, which is declared ‘register’ [-Wextra]

C ++ 11 brachte uns Lambdas, die in dieser Situation unglaublich nützlich sein kann:

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

Sie halten die Zeugungskraft von Makros, haben aber einen bequemen Rahmen, von dem Sie zurückkehren können, was Sie (einschließlich void) möchten. Darüber hinaus wird die Frage der Bewertung des Makroparameter mehrmals vermieden.

Erstellen Sie einen Block mit

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

ein nicht fügen Sie; nach der while (false)

Ihre Antwort leidet an dem Multiple-Auswertung Problem, so (zB)

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

tun etwas Unerwartetes und wahrscheinlich unerwünscht.

Wie andere erwähnt haben, sollten Sie Makros vermeiden, wann immer möglich. Sie sind gefährlich in Gegenwart von Nebenwirkungen, wenn die Makroargumente mehr als einmal ausgewertet werden. Wenn Sie die Art der Argumente kennen (oder C ++ 0x auto Funktion verwenden), können Sie Provisorien verwenden, um einzelne Auswertung zu erzwingen.

Ein weiteres Problem: die Reihenfolge, in der mehr Auswertungen passieren kann nicht sein, was Sie erwarten,

!

Betrachten Sie diesen Code ein:

#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;
}

Und es ist ausgegeben, wie auf meinem Rechner kompiliert und ausgeführt:

X=100, Y=10000, X+Y=110
X=10, Y=100, X+Y=110

Wenn Sie bereit sind, um die Praxis zu übernehmen, immer geschweifte Klammern in Ihre if-Anweisungen verwenden,

Das Makro würde einfach das letzte Semikolon fehlt:

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

Beispiel 1: (compiliert)

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

Beispiel 2: (compiliert)

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

Beispiel 3: (nicht kompilieren)

do_something();
MACRO(x, y)
do_something();
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top