Wann sind C ++ - Makros von Vorteil? [geschlossen]


176

Der C- Präprozessor wird von der C ++ - Community zu Recht gefürchtet und gemieden. Inline-Funktionen, Konstanten und Vorlagen sind normalerweise eine sicherere und überlegene Alternative zu a #define.

Das folgende Makro:

#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)  

ist dem Typ sicher in keiner Weise überlegen:

inline bool succeeded(int hr) { return hr >= 0; }

Aber Makros haben ihren Platz. Bitte listen Sie die Verwendungszwecke für Makros auf, die Sie ohne den Präprozessor nicht ausführen können.

Bitte geben Sie jeden Anwendungsfall in eine separate Antwort ein, damit über ihn abgestimmt werden kann. Wenn Sie wissen, wie Sie eine der Antworten ohne den Präprozessor erreichen können, geben Sie in den Kommentaren dieser Antwort an, wie.


Ich habe einmal eine C ++ - Anwendung voller Makros genommen, deren Erstellung 45 Minuten dauerte, die Makros durch Inline-Funktionen ersetzt und die Erstellung auf weniger als 15 Minuten reduziert.
Endian


Der Thread handelt von Kontexten, in denen Makros nützlich sind, nicht von Kontexten, in denen sie nicht optimal sind.
underscore_d

Antworten:


123

Als Wrapper für Debug - Funktionen, übergeben , um automatisch Dinge wie __FILE__, __LINE__etc:

#ifdef ( DEBUG )
#define M_DebugLog( msg )  std::cout << __FILE__ << ":" << __LINE__ << ": " << msg
#else
#define M_DebugLog( msg )
#endif

14
Tatsächlich ist das ursprüngliche Snippet: << FILE ":" << in Ordnung. FILE generiert eine Zeichenfolgenkonstante, die vom Vorprozessor mit dem ":" zu einer einzelnen Zeichenfolge verkettet wird.
Frank Szczerba

12
Dies erfordert nur den Präprozessor, weil __FILE__und __LINE__ auch den Präprozessor. Die Verwendung in Ihrem Code ist wie ein Infektionsvektor für den Präprozessor.
TED

93

Methoden müssen immer vollständiger, kompilierbarer Code sein; Makros können Codefragmente sein. So können Sie ein foreach-Makro definieren:

#define foreach(list, index) for(index = 0; index < list.size(); index++)

Und benutze es so:

foreach(cookies, i)
    printf("Cookie: %s", cookies[i]);

Seit C ++ 11 wird dies durch die bereichsbasierte for-Schleife ersetzt .


6
+1 Wenn Sie eine lächerlich komplexe Iteratorsyntax verwenden, kann das Schreiben eines foreach-Stilmakros das Lesen und Verwalten Ihres Codes erheblich vereinfachen. Ich habe es geschafft, es funktioniert.
Postfuturist

9
Die meisten Kommentare sind völlig irrelevant, bis zu dem Punkt, dass Makros Codefragmente anstelle von vollständigem Code sein können. Aber danke für das Nitpicking.
jdmichal

12
Dies ist C nicht C ++. Wenn Sie C ++ ausführen, sollten Sie Iteratoren und std :: for_each verwenden.
Chrish

20
Ich bin anderer Meinung, chrish. Vor Lambda for_eachwar das eine böse Sache, da der Code, durch den jedes Element lief, nicht lokal für den aufrufenden Punkt war. foreach, (und ich empfehle dringend, BOOST_FOREACHanstelle einer handgerollten Lösung) den Code in der Nähe der Iterationssite zu halten, damit er besser lesbar ist. Das heißt, sobald Lambda herauskommt, for_eachkönnte es wieder der richtige Weg sein.
GManNickG

8
Und es ist erwähnenswert, dass BOOST_FOREACH selbst ein Makro ist (aber ein sehr gut durchdachtes)
Tyler McHenry

59

Header File Guards erfordern Makros.

Gibt es andere Bereiche, in denen Makros erforderlich sind ? Nicht viele (wenn überhaupt).

Gibt es andere Situationen, die von Makros profitieren? JA!!!

Ein Ort, an dem ich Makros verwende, ist mit sich sehr wiederholendem Code. Wenn ich beispielsweise C ++ - Code für andere Schnittstellen (.NET, COM, Python usw.) einbinde, muss ich verschiedene Arten von Ausnahmen abfangen. So mache ich das:

#define HANDLE_EXCEPTIONS \
catch (::mylib::exception& e) { \
    throw gcnew MyDotNetLib::Exception(e); \
} \
catch (::std::exception& e) { \
    throw gcnew MyDotNetLib::Exception(e, __LINE__, __FILE__); \
} \
catch (...) { \
    throw gcnew MyDotNetLib::UnknownException(__LINE__, __FILE__); \
}

Ich muss diese Fänge in jede Wrapper-Funktion einfügen. Anstatt jedes Mal die vollständigen Fangblöcke auszutippen, tippe ich einfach:

void Foo()
{
    try {
        ::mylib::Foo()
    }
    HANDLE_EXCEPTIONS
}

Dies erleichtert auch die Wartung. Wenn ich jemals einen neuen Ausnahmetyp hinzufügen muss, muss ich ihn nur an einer Stelle hinzufügen.

Es gibt auch andere nützliche Beispiele: Viele davon enthalten die Makros __FILE__und und den __LINE__Präprozessor-Makros.

Auf jeden Fall sind Makros sehr nützlich, wenn sie richtig verwendet werden. Makros sind nicht böse - ihr Missbrauch ist böse.


7
Die meisten Compiler unterstützen #pragma oncediese Tage, daher bezweifle ich, dass Wachen wirklich notwendig sind
1800 INFORMATION

13
Sie sind, wenn Sie für alle Compiler schreiben, anstatt nur für die meisten ;-)
Steve Jessop

30
Anstelle einer tragbaren Standard-Präprozessorfunktionalität empfehlen Sie daher die Verwendung einer Präprozessorerweiterung, um die Verwendung des Präprozessors zu vermeiden. Kommt mir irgendwie lächerlich vor.
Logan Capaldo

#pragma oncebricht auf vielen gängigen Build-Systemen.
Miles Rout

4
Dafür gibt es eine Lösung, für die keine Makros erforderlich sind : void handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }. Und auf der Funktionsseite:void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }
MikeMB

51

Meist:

  1. Wachen einschließen
  2. Bedingte Kompilierung
  3. Berichterstellung (vordefinierte Makros wie __LINE__und __FILE__)
  4. (selten) Duplizieren sich wiederholender Codemuster.
  5. Im Code Ihres Konkurrenten.

Suchen Sie Hilfe bei der Realisierung von Nummer 5. Können Sie mich zu einer Lösung führen?
Max

50

Innerhalb der bedingten Kompilierung, um Probleme mit Unterschieden zwischen Compilern zu überwinden:

#ifdef ARE_WE_ON_WIN32
#define close(parm1)            _close (parm1)
#define rmdir(parm1)            _rmdir (parm1)
#define mkdir(parm1, parm2)     _mkdir (parm1)
#define access(parm1, parm2)    _access(parm1, parm2)
#define create(parm1, parm2)    _creat (parm1, parm2)
#define unlink(parm1)           _unlink(parm1)
#endif

12
In C ++ kann dasselbe durch die Verwendung von Inline-Funktionen erreicht werden: <code> #ifdef ARE_WE_ON_WIN32 <br> inline int close (int i) {return _close (i); } <br> #endif </ code>
paercebal

2
Dadurch werden die # Definitionen entfernt, nicht jedoch die #ifdef und #endif. Wie auch immer, ich stimme dir zu.
Gorpik

19
Definieren Sie NIEMALS Makros in Kleinbuchstaben. Makros zum Ändern von Funktionen sind mein Albtraum (danke Microsoft). Bestes Beispiel ist in der ersten Zeile. Viele Bibliotheken haben closeFunktionen oder Methoden. Wenn Sie dann den Header dieser Bibliothek und den Header in dieses Makro aufnehmen, als Sie ein großes Problem haben, können Sie die Bibliotheks-API nicht verwenden.
Marek R

AndrewStein, sehen Sie in diesem Zusammenhang einen Vorteil für die Verwendung von Makros gegenüber dem Vorschlag von @ paercebal? Wenn nicht, scheinen Makros tatsächlich unbegründet zu sein.
Einpoklum

1
#ifdef WE_ARE_ON_WIN32PLZ :)
Leichtigkeit Rennen im Orbit

38

Wenn Sie aus einem Ausdruck eine Zeichenfolge machen möchten, ist das beste Beispiel dafür assert( #xwandelt den Wert von xin eine Zeichenfolge um).

#define ASSERT_THROW(condition) \
if (!(condition)) \
     throw std::exception(#condition " is false");

5
Nur ein Trottel, aber ich persönlich würde das Semikolon weglassen.
Michael Myers

10
Ich stimme zu, tatsächlich würde ich es in eine do {} while (false) setzen (um sonst Highjacking zu verhindern), aber ich wollte es einfach halten.
Motti

32

String-Konstanten werden manchmal besser als Makros definiert, da Sie mit String-Literalen mehr tun können als mit a const char *.

zB String-Literale können leicht verkettet werden .

#define BASE_HKEY "Software\\Microsoft\\Internet Explorer\\"
// Now we can concat with other literals
RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY "Settings", &settings);
RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY "TypedURLs", &URLs);

Wenn a const char *verwendet würde, müsste eine Art Zeichenfolgenklasse verwendet werden, um die Verkettung zur Laufzeit durchzuführen:

const char* BaseHkey = "Software\\Microsoft\\Internet Explorer\\";
RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) + "Settings").c_str(), &settings);
RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) + "TypedURLs").c_str(), &URLs);

2
In C ++ 11 würde ich dies als den wichtigsten Teil betrachten (abgesehen von Wachen einschließen). Makros sind wirklich das Beste, was wir für die Verarbeitung von Zeichenfolgen zur Kompilierungszeit haben. Das ist eine Funktion, die wir hoffentlich in C ++ 11 ++
David Stone

1
Dies ist die Situation, die dazu führte, dass ich mir Makros in C # wünschte.
Rawling

2
Ich wünschte, ich könnte dies +42. Ein sehr wichtiger, wenn auch nicht oft in Erinnerung gebliebener Aspekt von String-Literalen.
Daniel Kamil Kozar

24

Wenn Sie den Programmablauf ändern ( return, breakund continue) Code in einer Funktion verhält sich anders als Code, der tatsächlich in der Funktion inlined wird.

#define ASSERT_RETURN(condition, ret_val) \
if (!(condition)) { \
    assert(false && #condition); \
    return ret_val; }

// should really be in a do { } while(false) but that's another discussion.

Eine Ausnahme zu werfen scheint mir eine bessere Alternative zu sein.
Einpoklum

Beim Schreiben von Python C (++) - Erweiterungen werden Ausnahmen weitergegeben, indem eine Ausnahmezeichenfolge festgelegt und dann -1oder zurückgegeben wird NULL. Ein Makro kann dort also den Boilerplate-Code erheblich reduzieren.
black_puppydog

20

Zu den offensichtlichen gehören Wachen

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif

17

Sie können Funktionsaufrufargumente nicht mit einem regulären Funktionsaufruf kurzschließen. Beispielsweise:

#define andm(a, b) (a) && (b)

bool andf(bool a, bool b) { return a && b; }

andm(x, y) // short circuits the operator so if x is false, y would not be evaluated
andf(x, y) // y will always be evaluated

3
Vielleicht ein allgemeinerer Punkt: Funktionen bewerten ihre Argumente genau einmal. Makros können Argumente mehrmals oder seltener auswerten.
Steve Jessop

@ [Greg Rogers] Der Makro-Präprozessor ersetzt lediglich Text. Sobald Sie das verstanden haben, sollte es kein Geheimnis mehr geben.
1800 INFORMATION

Sie können das entsprechende Verhalten erhalten, indem Sie andf templatisieren, anstatt die Auswertung vor dem Aufrufen der Funktion auf bool zu setzen. Ich hätte nicht bemerkt, dass das, was Sie gesagt haben, wahr ist, ohne es selbst zu versuchen. Interessant.
Greg Rogers

Wie genau könnten Sie das mit einer Vorlage machen?
1800 INFORMATION

6
Das Verstecken von Kurzschlussoperationen hinter einem Funktionsstilmakro ist eines der Dinge, die ich im Produktionscode wirklich nicht sehen möchte.
MikeMB

17

Nehmen wir an, wir ignorieren offensichtliche Dinge wie Header Guards.

Manchmal möchten Sie Code generieren, der vom Precompiler kopiert / eingefügt werden muss:

#define RAISE_ERROR_STL(p_strMessage)                                          \
do                                                                             \
{                                                                              \
   try                                                                         \
   {                                                                           \
      std::tstringstream strBuffer ;                                           \
      strBuffer << p_strMessage ;                                              \
      strMessage = strBuffer.str() ;                                           \
      raiseSomeAlert(__FILE__, __FUNCSIG__, __LINE__, strBuffer.str().c_str()) \
   }                                                                           \
   catch(...){}                                                                \
   {                                                                           \
   }                                                                           \
}                                                                              \
while(false)

Damit können Sie Folgendes codieren:

RAISE_ERROR_STL("Hello... The following values " << i << " and " << j << " are wrong") ;

Und kann Nachrichten generieren wie:

Error Raised:
====================================
File : MyFile.cpp, line 225
Function : MyFunction(int, double)
Message : "Hello... The following values 23 and 12 are wrong"

Beachten Sie, dass das Mischen von Vorlagen mit Makros zu noch besseren Ergebnissen führen kann (dh die Werte werden automatisch neben ihren Variablennamen generiert).

In anderen Fällen benötigen Sie die __FILE__ und / oder die __LINE__ eines Codes, um beispielsweise Debug-Informationen zu generieren. Das Folgende ist ein Klassiker für Visual C ++:

#define WRNG_PRIVATE_STR2(z) #z
#define WRNG_PRIVATE_STR1(x) WRNG_PRIVATE_STR2(x)
#define WRNG __FILE__ "("WRNG_PRIVATE_STR1(__LINE__)") : ------------ : "

Wie beim folgenden Code:

#pragma message(WRNG "Hello World")

Es generiert Nachrichten wie:

C:\my_project\my_cpp_file.cpp (225) : ------------ Hello World

In anderen Fällen müssen Sie Code mit den Verkettungsoperatoren # und ## generieren, z. B. Getter und Setter für eine Eigenschaft generieren (dies gilt nur für sehr begrenzte Fälle).

In anderen Fällen generieren Sie Code, der bei Verwendung über eine Funktion nicht kompiliert werden kann, z.

#define MY_TRY      try{
#define MY_CATCH    } catch(...) {
#define MY_END_TRY  }

Welches kann als verwendet werden

MY_TRY
   doSomethingDangerous() ;
MY_CATCH
   tryToRecoverEvenWithoutMeaningfullInfo() ;
   damnThoseMacros() ;
MY_END_TRY

(Trotzdem habe ich diese Art von Code nur einmal richtig gesehen )

Zu guter Letzt das berühmte boost::foreach!!!

#include <string>
#include <iostream>
#include <boost/foreach.hpp>

int main()
{
    std::string hello( "Hello, world!" );

    BOOST_FOREACH( char ch, hello )
    {
        std::cout << ch;
    }

    return 0;
}

(Hinweis: Code kopieren / von der Boost-Homepage einfügen)

Welches ist (IMHO) viel besser als std::for_each.

Makros sind also immer nützlich, da sie außerhalb der normalen Compilerregeln liegen. Aber ich finde, dass die meiste Zeit, die ich sehe, tatsächlich Reste von C-Code sind, der nie in richtiges C ++ übersetzt wurde.


1
Verwenden Sie den CPP nur für das, was der Compiler nicht kann. Beispielsweise sollte RAISE_ERROR_STL den CPP nur zum Bestimmen der Datei-, Zeilen- und Funktionssignatur verwenden und diese an eine Funktion (möglicherweise inline) übergeben, die den Rest erledigt.
Rainer Blome

Bitte aktualisieren Sie Ihre Antwort entsprechend C ++ 11 und adressieren Sie den Kommentar von @ RainerBlome.
Einpoklum

@RainerBlome: Wir sind uns einig. Das Makro RAISE_ERROR_STL ist vor C ++ 11, daher ist es in diesem Zusammenhang vollständig gerechtfertigt. Mein Verständnis (aber ich hatte noch nie die Gelegenheit, mich mit diesen spezifischen Funktionen zu befassen) ist, dass Sie in Modern C ++ verschiedene Vorlagen (oder Makros?) Verwenden können, um das Problem eleganter zu lösen.
Paercebal

@einpoklum: "Bitte aktualisieren Sie Ihre Antwort, um C ++ 11 wiederzugeben, und adressieren Sie den Kommentar von RainerBlome" Nein. :-). . . Ich glaube, ich werde bestenfalls einen Abschnitt für Modern C ++ hinzufügen, in dem alternative Implementierungen die Notwendigkeit von Makros reduzieren oder eliminieren, aber der Punkt ist: Makros sind hässlich und böse, aber wenn Sie etwas tun müssen, versteht der Compiler dies nicht , du machst es über Makros.
Paercebal

Selbst mit C ++ 11 kann vieles, was Ihr Makro tut, für eine Funktion übrig bleiben: Auf #include <sstream> #include <iostream> using namespace std; void trace(char const * file, int line, ostream & o) { cerr<<file<<":"<<line<<": "<< static_cast<ostringstream & >(o).str().c_str()<<endl; } struct Oss { ostringstream s; ostringstream & lval() { return s; } }; #define TRACE(ostreamstuff) trace(__FILE__, __LINE__, Oss().lval()<<ostreamstuff) int main() { TRACE("Hello " << 123); return 0; }diese Weise ist das Makro viel kürzer.
Rainer Blome

16

Unit-Test-Frameworks für C ++ wie UnitTest ++ drehen sich so ziemlich um Präprozessor-Makros. Ein paar Zeilen Unit-Test-Code erweitern sich zu einer Hierarchie von Klassen, deren manuelle Eingabe überhaupt keinen Spaß machen würde. Ohne etwas wie UnitTest ++ und seine Präprozessor-Magie weiß ich nicht, wie Sie Unit-Tests für C ++ effizient schreiben würden.


Unittests können durchaus ohne Rahmen geschrieben werden. Am Ende kommt es nur wirklich darauf an, welche Art von Ausgabe Sie möchten. Wenn es Sie nicht interessiert, sollte ein einfacher Exit-Wert, der Erfolg oder Misserfolg anzeigt, vollkommen in Ordnung sein.
Klarer

15

Den C-Präprozessor zu fürchten ist wie die Glühlampen zu fürchten, nur weil wir Leuchtstofflampen bekommen. Ja, ersteres kann {Strom | sein Programmierzeit} ineffizient. Ja, Sie können sich (buchstäblich) von ihnen verbrennen lassen. Aber sie können die Arbeit erledigen, wenn Sie richtig damit umgehen.

Wenn Sie eingebettete Systeme programmieren, ist C neben dem Form Assembler die einzige Option. Nachdem Sie mit C ++ auf dem Desktop programmiert und dann zu kleineren, eingebetteten Zielen gewechselt haben, lernen Sie, sich keine Gedanken mehr über „Uneleganzen“ so vieler nackter C-Funktionen (einschließlich Makros) zu machen und nur noch herauszufinden, wie Sie diese am besten und sichersten nutzen können Eigenschaften.

Alexander Stepanov sagt :

Wenn wir in C ++ programmieren, sollten wir uns nicht für das C-Erbe schämen, sondern es voll ausnutzen. Die einzigen Probleme mit C ++ und sogar die einzigen Probleme mit C treten auf, wenn sie selbst nicht mit ihrer eigenen Logik übereinstimmen.


Ich denke das ist die falsche Einstellung. Nur weil Sie lernen können, "richtig damit umzugehen", heißt das nicht, dass es die Zeit und Mühe eines jeden wert ist.
Neil G

9

Wir verwenden die Makros __FILE__und __LINE__für Diagnosezwecke beim Auslösen, Abfangen und Protokollieren von Informationen mit zahlreichen Ausnahmen sowie automatisierte Protokolldateiscanner in unserer QS-Infrastruktur.

Beispielsweise kann ein Auslösemakro OUR_OWN_THROWmit Ausnahmetyp- und Konstruktorparametern für diese Ausnahme verwendet werden, einschließlich einer Textbeschreibung. So was:

OUR_OWN_THROW(InvalidOperationException, (L"Uninitialized foo!"));

Dieses Makro löst natürlich die InvalidOperationExceptionAusnahme mit der Beschreibung als Konstruktorparameter aus, schreibt aber auch eine Nachricht in eine Protokolldatei, die aus dem Dateinamen und der Zeilennummer, in der der Wurf stattgefunden hat, und der Textbeschreibung besteht. Die ausgelöste Ausnahme erhält eine ID, die ebenfalls protokolliert wird. Wenn die Ausnahme jemals an einer anderen Stelle im Code abgefangen wird, wird sie als solche markiert, und die Protokolldatei zeigt an, dass diese bestimmte Ausnahme behandelt wurde und daher wahrscheinlich nicht die Ursache für einen Absturz ist, der später protokolliert wird. Nicht behandelte Ausnahmen können von unserer automatisierten QS-Infrastruktur problemlos erfasst werden.


9

Code-Wiederholung.

Schauen Sie sich die Präprozessor-Bibliothek an , es ist eine Art Meta-Meta-Programmierung. In Thema-> Motivation finden Sie ein gutes Beispiel.


Ich fast alle, wenn nicht alle Fälle - Code-Wiederholungen können mit Funktionsaufrufen vermieden werden.
Einpoklum

@einpoklum: Ich stimme nicht zu. Werfen Sie einen Blick auf den Link
Ruggero Turra

9

Einige sehr fortgeschrittene und nützliche Dinge können immer noch mit Präprozessoren (Makros) erstellt werden, was Sie mit den c ++ - "Sprachkonstrukten" einschließlich Vorlagen niemals tun könnten.

Beispiele:

Machen Sie etwas sowohl zu einem C-Bezeichner als auch zu einer Zeichenfolge

Einfache Möglichkeit, Variablen von Aufzählungstypen als Zeichenfolge in C zu verwenden

Boost Preprocessor Metaprogramming


Die dritte Verbindung ist
Robin Hartland

Werfen Sie einen Blick darauf stdio.hund sal.hlegen Sie ihn ab, vc12um ihn besser zu verstehen.
Elshan

7

Ich verwende gelegentlich Makros, damit ich Informationen an einem Ort definieren kann, aber in verschiedenen Teilen des Codes auf unterschiedliche Weise. Es ist nur leicht böse :)

Zum Beispiel in "field_list.h":

/*
 * List of fields, names and values.
 */
FIELD(EXAMPLE1, "first example", 10)
FIELD(EXAMPLE2, "second example", 96)
FIELD(ANOTHER, "more stuff", 32)
...
#undef FIELD

Dann kann für eine öffentliche Aufzählung definiert werden, dass nur der Name verwendet wird:

#define FIELD(name, desc, value) FIELD_ ## name,

typedef field_ {

#include "field_list.h"

    FIELD_MAX

} field_en;

In einer privaten Init-Funktion können alle Felder verwendet werden, um eine Tabelle mit den Daten zu füllen:

#define FIELD(name, desc, value) \
    table[FIELD_ ## name].desc = desc; \
    table[FIELD_ ## name].value = value;

#include "field_list.h"

1
Hinweis: Eine ähnliche Technik kann auch ohne separates Include implementiert werden. Siehe: stackoverflow.com/questions/147267/… stackoverflow.com/questions/126277/…
Suma

6

Eine häufige Verwendung ist das Erkennen der Kompilierungsumgebung. Für die plattformübergreifende Entwicklung können Sie beispielsweise einen Satz Code für Linux und einen anderen für Windows schreiben, wenn für Ihre Zwecke noch keine plattformübergreifende Bibliothek vorhanden ist.

In einem groben Beispiel kann also ein plattformübergreifender Mutex vorhanden sein

void lock()
{
    #ifdef WIN32
    EnterCriticalSection(...)
    #endif
    #ifdef POSIX
    pthread_mutex_lock(...)
    #endif
}

Für Funktionen sind sie nützlich, wenn Sie die Typensicherheit explizit ignorieren möchten. Wie die vielen Beispiele oben und unten für ASSERT. Natürlich können Sie sich wie bei vielen C / C ++ - Funktionen selbst in den Fuß schießen, aber die Sprache gibt Ihnen die Werkzeuge und lässt Sie entscheiden, was zu tun ist.


Da fragte der Fragesteller: Dies kann ohne Makros erfolgen, indem verschiedene Header über verschiedene Include-Pfade pro Plattform eingeschlossen werden. Ich bin jedoch geneigt zuzustimmen, dass Makros oft bequemer sind.
Steve Jessop

Ich stimme dem zu. Wenn Sie anfangen, Makros für diesen Zweck zu verwenden, kann der Code schnell viel weniger lesbar werden
Nemanja Trifunovic

6

Etwas wie

void debugAssert(bool val, const char* file, int lineNumber);
#define assert(x) debugAssert(x,__FILE__,__LINE__);

So dass Sie nur zum Beispiel haben können

assert(n == true);

und erhalten Sie den Namen der Quelldatei und die Zeilennummer des Problems in Ihrem Protokoll, wenn n falsch ist.

Wenn Sie einen normalen Funktionsaufruf wie z

void assert(bool val);

Anstelle des Makros können Sie nur die Zeilennummer Ihrer Assert-Funktion erhalten, die in das Protokoll gedruckt wird, was weniger nützlich wäre.


Warum sollten Sie das Rad neu erfinden, wenn Implementierungen der Standardbibliothek bereits über <cassert>das assert()Makro bereitgestellt werden, das die Datei- / Zeilen- / Funktionsinformationen ausgibt? (in allen Implementierungen, die ich sowieso gesehen habe)
underscore_d

4
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

Im Gegensatz zu der in einem aktuellen Thread beschriebenen "bevorzugten" Vorlagenlösung können Sie sie als konstanten Ausdruck verwenden:

char src[23];
int dest[ARRAY_SIZE(src)];

2
Dies kann mit Vorlagen auf sicherere Weise erfolgen (die nicht kompiliert werden, wenn ein Zeiger anstelle eines Arrays übergeben wird) stackoverflow.com/questions/720077/calculating-size-of-an-array/…
Motti

1
Nachdem wir in C ++ 11 constexpr haben, kann die sichere (Nicht-Makro-) Version auch in einem konstanten Ausdruck verwendet werden. template<typename T, std::size_t size> constexpr std::size_t array_size(T const (&)[size]) { return size; }
David Stone

3

Sie können #defines verwenden, um beim Debuggen und bei Unit-Testszenarien zu helfen. Erstellen Sie beispielsweise spezielle Protokollierungsvarianten der Speicherfunktionen und erstellen Sie eine spezielle memlog_preinclude.h:

#define malloc memlog_malloc
#define calloc memlog calloc
#define free memlog_free

Kompilieren Sie Ihren Code mit:

gcc -Imemlog_preinclude.h ...

Ein Link in Ihrer memlog.o zum endgültigen Bild. Sie steuern jetzt malloc usw., möglicherweise zu Protokollierungszwecken oder um Simulationsfehler für Komponententests zu simulieren.


3

Wenn Sie zur Kompilierungszeit eine Entscheidung über das Compiler / OS / Hardware-spezifische Verhalten treffen.

Damit können Sie Ihre Schnittstelle zu Comppiler / OS / Hardware-spezifischen Funktionen gestalten.

#if defined(MY_OS1) && defined(MY_HARDWARE1)
#define   MY_ACTION(a,b,c)      doSothing_OS1HW1(a,b,c);}
#elif define(MY_OS1) && defined(MY_HARDWARE2)
#define   MY_ACTION(a,b,c)      doSomthing_OS1HW2(a,b,c);}
#elif define(MY_SUPER_OS)
          /* On this hardware it is a null operation */
#define   MY_ACTION(a,b,c)
#else
#error  "PLEASE DEFINE MY_ACTION() for this Compiler/OS/HArdware configuration"
#endif

3

Ich benutze Makros, um Ausnahmen einfach zu definieren:

DEF_EXCEPTION(RessourceNotFound, "Ressource not found")

wo DEF_EXCEPTION ist

#define DEF_EXCEPTION(A, B) class A : public exception\
  {\
  public:\
    virtual const char* what() const throw()\
    {\
      return B;\
    };\
  }\

2

Compiler können Ihre Inline-Anfrage ablehnen.

Makros werden immer ihren Platz haben.

Etwas, das ich nützlich finde, ist #define DEBUG für die Debug-Ablaufverfolgung - Sie können es 1 lassen, während Sie ein Problem debuggen (oder es sogar während des gesamten Entwicklungszyklus aktiviert lassen) und es dann ausschalten, wenn es Zeit für den Versand ist.


10
Wenn der Compiler Ihre Inline-Anfrage ablehnt, kann dies einen sehr guten Grund haben. Ein guter Compiler kann besser richtig inlinieren als Sie, und ein schlechter Compiler führt zu mehr Leistungsproblemen.
David Thornley

@DavidThornley Oder es ist kein großartiger Optimierungs-Compiler wie GCC oder CLANG / LLVM. Einige Compiler sind nur Mist.
Miles Rout

2

In meinem letzten Job habe ich an einem Virenscanner gearbeitet. Um mir das Debuggen zu erleichtern, hatte ich überall viel Protokollierung, aber in einer solchen App mit hoher Nachfrage sind die Kosten für einen Funktionsaufruf einfach zu hoch. Also habe ich mir dieses kleine Makro ausgedacht, mit dem ich immer noch die Debug-Protokollierung für eine Release-Version bei einem Kunden aktivieren konnte, ohne dass die Kosten eines Funktionsaufrufs das Debug-Flag überprüfen und einfach zurückkehren würden, ohne etwas zu protokollieren, oder falls aktiviert , würde die Protokollierung durchführen ... Das Makro wurde wie folgt definiert:

#define dbgmsg(_FORMAT, ...)  if((debugmsg_flag  & 0x00000001) || (debugmsg_flag & 0x80000000))     { log_dbgmsg(_FORMAT, __VA_ARGS__);  }

Aufgrund der VA_ARGS in den Protokollfunktionen war dies ein guter Fall für ein Makro wie dieses.

Zuvor habe ich in einer Hochsicherheitsanwendung ein Makro verwendet, das dem Benutzer mitteilen musste, dass er nicht über den richtigen Zugriff verfügt, und das ihm mitteilte, welches Flag er benötigt.

Die Makros sind definiert als:

#define SECURITY_CHECK(lRequiredSecRoles) if(!DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, true)) return
#define SECURITY_CHECK_QUIET(lRequiredSecRoles) (DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, false))

Dann könnten wir die Überprüfungen einfach über die gesamte Benutzeroberfläche streuen und Ihnen mitteilen, welche Rollen die von Ihnen versuchte Aktion ausführen dürfen, wenn Sie diese Rolle noch nicht haben. Der Grund für zwei von ihnen war, an einigen Stellen einen Wert zurückzugeben und an anderen von einer leeren Funktion zurückzukehren ...

SECURITY_CHECK(ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR);

LRESULT CAddPerson1::OnWizardNext() 
{
   if(m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_EMPLOYEE) {
      SECURITY_CHECK(ROLE_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD ) -1;
   } else if(m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_CONTINGENT) {
      SECURITY_CHECK(ROLE_CONTINGENT_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR) -1;
   }
...

Wie auch immer, so habe ich sie verwendet, und ich bin mir nicht sicher, wie dies mit Vorlagen hätte geholfen werden können ... Abgesehen davon versuche ich, sie zu vermeiden, es sei denn, dies ist WIRKLICH notwendig.


2

Noch ein foreach-Makro. T: Typ, c: Container, i: Iterator

#define foreach(T, c, i) for(T::iterator i=(c).begin(); i!=(c).end(); ++i)
#define foreach_const(T, c, i) for(T::const_iterator i=(c).begin(); i!=(c).end(); ++i)

Verwendung (Konzept zeigt, nicht real):

void MultiplyEveryElementInList(std::list<int>& ints, int mul)
{
    foreach(std::list<int>, ints, i)
        (*i) *= mul;
}

int GetSumOfList(const std::list<int>& ints)
{
    int ret = 0;
    foreach_const(std::list<int>, ints, i)
        ret += *i;
    return ret;
}

Bessere Implementierungen verfügbar: Google "BOOST_FOREACH"

Gute Artikel verfügbar: Bedingte Liebe: FOREACH Redux (Eric Niebler) http://www.artima.com/cppsource/foreach.html


2

Möglicherweise liegt die größte Verwendung von Makros in der plattformunabhängigen Entwicklung. Denken Sie an Fälle von Typinkonsistenz - mit Makros können Sie einfach verschiedene Header-Dateien verwenden - wie: --WIN_TYPES.H

typedef ...some struct

--POSIX_TYPES.h

typedef ...some another struct

--program.h

#ifdef WIN32
#define TYPES_H "WINTYPES.H"
#else 
#define TYPES_H "POSIX_TYPES.H"
#endif

#include TYPES_H

Meiner Meinung nach viel lesbarer als auf andere Weise umzusetzen.


2

VA_ARGS wurden bisher anscheinend nur indirekt erwähnt:

Wenn Sie generischen C ++ 03-Code schreiben und eine variable Anzahl von (generischen) Parametern benötigen, können Sie anstelle einer Vorlage ein Makro verwenden.

#define CALL_RETURN_WRAPPER(FnType, FName, ...)          \
  if( FnType theFunction = get_op_from_name(FName) ) {   \
    return theFunction(__VA_ARGS__);                     \
  } else {                                               \
    throw invalid_function_name(FName);                  \
  }                                                      \
/**/

Hinweis: Im Allgemeinen kann der Name check / throw auch in die hypothetische get_op_from_nameFunktion integriert werden. Dies ist nur ein Beispiel. Möglicherweise enthält der VA_ARGS-Aufruf einen anderen generischen Code.

Sobald wir mit C ++ 11 verschiedene Vorlagen erhalten haben, können wir diese "richtig" mit einer Vorlage lösen.


1

Ich denke, dieser Trick ist eine clevere Verwendung des Präprozessors, die mit einer Funktion nicht emuliert werden kann:

#define COMMENT COMMENT_SLASH(/)
#define COMMENT_SLASH(s) /##s

#if defined _DEBUG
#define DEBUG_ONLY
#else
#define DEBUG_ONLY COMMENT
#endif

Dann können Sie es so verwenden:

cout <<"Hello, World!" <<endl;
DEBUG_ONLY cout <<"This is outputed only in debug mode" <<endl;

Sie können auch ein RELEASE_ONLY-Makro definieren.


2
Dieser Trick funktioniert nicht gemäß dem Standard. Es wird versucht, eine Kommentarmarkierung über den Präprozessor zu erstellen. Kommentare müssen jedoch entfernt werden, bevor der Präprozessor ausgeführt wird. Ein konformer Compiler verursacht hier einen Syntaxfehler.
David Thornley

2
Sorry David, aber der Compiler muss eine zweite Kopie der Kommentarentfernung enthalten.
Joshua

Viel einfacher ist es, das Debugging-Flag zu einem globalen const bool zu machen und folgenden Code zu verwenden: if (debug) cout << "..."; - keine Makros erforderlich!
Stefan Monov

@Stefan: In der Tat ist es das, was ich jetzt mache. Jeder anständige Compiler generiert keinen Code, wenn das Debug in diesem Fall falsch ist.
Mathieu Pagé

1

Sie können #defineKonstanten in der Compiler-Befehlszeile mit der Option -Doder /Dfestlegen. Dies ist häufig nützlich, wenn Sie dieselbe Software für mehrere Plattformen übergreifend kompilieren, da Sie mit Ihren Makefiles steuern können, welche Konstanten für jede Plattform definiert werden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.