Frage

Das C Präprozessor wird zu Recht gefürchtet und von der C ++ Gemeinschaft gemieden. In gesäumte Funktionen, consts und Vorlagen sind in der Regel eine sicherere und bessere Alternative zu einem #define.

Das folgende Makro:

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

ist in keiner Weise überlegen Art sicher:

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

Aber Makros haben ihren Platz, geben Sie bitte die Verwendungen Sie für Makros, die Sie nicht tun, ohne den Präprozessor.

Bitte legen Sie jeweils Use-Cases in einer separaten Antwort so kann abgestimmt werden, und wenn Sie wissen, wie eine der Antworten zu erreichen, ohne die preprosessor darauf hinweisen, wie in dieser Antwort der Kommentare.

War es hilfreich?

Lösung

Als Wrapper für Debug-Funktionen, um automatisch Dinge wie __FILE__ passiert, __LINE__, etc:

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

Andere Tipps

Methoden immer vollständig, übersetzbar Code sein muss; Makros können Code-Fragmente sein. So können Sie eine foreach Makro definiert werden:

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

Und es verwendet, wie so:

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

Da C ++ 11, wird dies durch die abgelöst wird für Loop-Bereich-basierten .

Header-Datei Schutz erforderlich machen Makros.

Gibt es andere Bereiche, die necessitate Makros? Nicht viele (falls vorhanden).

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

Ein Ort, wo ich Makros ist mit sehr repetitiven Code. Wenn beispielsweise C ++ Code Umwickeln mit anderen Schnittstellen verwendet werden (.NET, COM, Python, etc ...), muss ich verschiedene Arten von Ausnahmen fangen. Hier ist, wie ich das tun:

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

muss ich diese Fänge in jedem Wrapper-Funktion setzen. Anstatt jedes Mal die vollen catch-Blöcke tippen, ich habe gerade ein:

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

Dies macht auch die Wartung einfacher. Wenn ich jemals einen neuen Ausnahmetyp hinzufügen muß, gibt es nur einen Ort, den ich brauche, um es hinzuzufügen.

Es gibt andere nützliche Beispiele zu: von denen viele gehören die __FILE__ und __LINE__ Präprozessormakros

.

Wie auch immer, Makros sind sehr nützlich, wenn richtig eingesetzt. Makros sind nicht böse -. Ihre Missbrauch ist böse

Meist:

  1. Neue Wächter
  2. Bedingtes Kompilieren
  3. Berichte (vordefinierte Makros wie __LINE__ und __FILE__)
  4. (selten) Duplizieren wiederholende Codemuster.
  5. In Ihrem Mitbewerber Code.

Innerhalb der bedingten Kompilierung, zu überwinden Fragen der Unterschiede zwischen den Compiler:

#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

Wenn Sie eine Zeichenfolge aus einem Ausdruck machen wollen, ist das beste Beispiel dafür ist assert (#x den Wert von x wendet sich an einen String).

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

String-Konstanten manchmal besser sind als Makros definiert, da Sie mehr mit Stringliterale als mit einem const char * tun können.

z. Stringliterale kann leicht verketteten .

#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 ein const char * wurde verwendet, um dann eine Art von String-Klasse hätte verwendet werden, um die Verkettung zur Laufzeit auszufü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);

Wenn Sie den Programmablauf (return, break und continue) Code in einer Funktion ändern möchten verhält sich anders als Code, der in der Funktion tatsächlich 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.

Die offensichtliche umfassen Wachen

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif

Sie können kein Kurzschließen Funktionsaufruf Argumente mit einem normalen Funktionsaufruf durchzuführen. Zum Beispiel:

#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

Lassen Sie uns sagen, dass wir offensichtliche Dinge wie Kopfschutz ignorieren werden.

Manchmal möchten Sie den Code generieren, die 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)

, die ermöglicht es Ihnen, diese zu codieren:

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

Und kann generieren Meldungen 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 Vorlagen mit Makros Mischen zu noch besseren Ergebnissen führen können (das heißt die Werte automatisch Seite-an-Seite mit ihren Variablennamen zu erzeugen)

Andere Zeiten, müssen Sie die __FILE__ und / oder die __LINE__ von Code, Debug-Informationen zu erzeugen, zum Beispiel. Im Folgenden 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 bei dem folgenden Code:

#pragma message(WRNG "Hello World")

es erzeugt Meldungen wie:

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

Andere Zeiten, müssen Sie Code mit dem # und ## Verkettung Operatoren erzeugen, wie Getter und Setter für eine Eigenschaft zu erzeugen (dies ist für einen ganz wenige Fälle durch).

Andere Zeiten, werden Sie Code generieren als nicht, wenn durch eine Funktion kompilieren, wie:

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

, die als

verwendet werden kann,
MY_TRY
   doSomethingDangerous() ;
MY_CATCH
   tryToRecoverEvenWithoutMeaningfullInfo() ;
   damnThoseMacros() ;
MY_END_TRY

(noch, ich sah nur diese Art von Code zu Recht verwendet einmal )

Last, but not least, die 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 eingefügt)

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

So, Makros sind immer nützlich, weil sie außerhalb der normalen Compiler Regeln sind. Aber ich finde, dass die meisten der Zeit, ich einen sehe, sind sie wirksam bleibt von C-Code niemals in die richtige C ++ übersetzt.

Unit-Test-Frameworks für C ++ wie Unittest ++ ziemlich drehen um Präprozessormakros. Ein paar Zeilen von Unit-Test-Code erweitern in eine Hierarchie von Klassen, die nicht Spaß machen würde, überhaupt manuell einzugeben. Ohne etwas wie Unittest ++ und es ist Präprozessor Magie, ich weiß nicht, wie Sie effizient Unit-Tests für C ++ schreiben würden.

den C-Präprozessor zu befürchten ist wie die Glühbirnen zu befürchten, nur weil wir Leuchtstoffröhren bekommen. Ja, der ehemalige sein {Strom | Programmierer Zeit} ineffizient. Ja, können Sie (buchstäblich) verbrannt von ihnen. Aber sie können den Job zu erledigen, wenn Sie richtig damit umgehen.

Wenn Sie Embedded-Systeme zu programmieren, verwendet C die einzige Option außer Form Assembler sein. Nach der Programmierung auf dem Desktop mit C ++ und dann zu kleineren, eingebettete Ziele wechseln, lernen Sie von so vielen nackten C Merkmale sich Gedanken über „inelegancies“ zu stoppen (Makros enthalten) und nur versuchen, die beste und sichere Nutzung, um herauszufinden, Sie von denen bekommen Funktionen.

Alexander Stepanov sagt :

  

Wenn wir in C ++ programmieren sollten wir nicht von seiner C Erbe schämen, sondern machen   voller Gebrauch davon. Die einzigen Probleme, mit C ++, und sogar die einzigen Probleme, mit C, entstehen   wenn sie sich nicht im Einklang mit ihrer eigenen Logik.

Wir verwenden den __FILE__ und __LINE__ Makros für diagnostische Zwecke in Information reicher Ausnahme Werfen, Fangen und Protokollierung, zusammen mit automatisierten Log-Datei-Scanner in unserer QA-Infrastruktur.

Zum Beispiel kann ein Makro zu werfen OUR_OWN_THROW könnte diese Ausnahme mit Ausnahme Typ und Konstruktorparameter verwendet werden, einschließlich einer Textbeschreibung. Wie folgt aus:

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

Dieses Makro wird natürlich die InvalidOperationException Ausnahme mit der Beschreibung als Konstruktorparameter werfen, aber es wird auch eine Nachricht in eine Protokolldatei schreiben, bestehend aus dem Dateinamen und die Zeilennummer, wo der Wurf aufgetreten und seine Textbeschreibung. Die ausgelöste Ausnahme wird eine ID erhalten, die wird auch protokolliert. Wenn die Ausnahme jemals irgendwo anders im Code gefangen wird, wird sie als solche gekennzeichnet sein und die Log-Datei wird dann zeigen, dass die spezifische Ausnahme behandelt wurde und dass es daher nicht wahrscheinlich die Ursache für jeden Absturz, der später angemeldet sein könnte. Unbehandelte Ausnahmen können durch unsere automatisierte QS-Infrastruktur leicht aufgenommen werden.

Code-Wiederholung.

Werfen Sie einen Blick auf Präprozessor Bibliothek steigern , es ist eine Art Meta-Meta-Programmierung. In themen-> Motivation können Sie ein gutes Beispiel finden.

Einige sehr weit fortgeschritten und nützliche Dinge noch gebaut werden können Präprozessor (Makros) verwenden, die Sie nie in der Lage wären, die c zu tun mit ++ „Sprachkonstrukte“ einschließlich Vorlagen.

Beispiele:

machen etwas sowohl eine C-Kennung und ein String

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

Preprocessor Erhöhung Metaprogrammierung

ich Makros gelegentlich verwenden, so dass ich Informationen an einem Ort definieren, aber es auf unterschiedliche Weise in verschiedenen Teilen des Codes verwenden. Es ist nur etwas evil:)

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 für eine öffentliche ENUM kann nur definiert werden, um den Namen zu verwenden:

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

typedef field_ {

#include "field_list.h"

    FIELD_MAX

} field_en;

Und in einer privaten init-Funktion, wird alle Felder verwendet werden können, eine Tabelle mit Daten zu füllen:

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

#include "field_list.h"

Eine häufige Verwendung ist für die Kompilierung Umgebung zu erfassen, für die plattformübergreifende Entwicklung Sie einen Satz von Code für Linux schreiben kann, sagen wir, und eine andere für Fenster, wenn keine Cross-Plattform-Bibliothek bereits für Ihre Zwecke vorhanden ist.

Also, in einem groben Beispiel ein Cross-Plattform-Mutex hat

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

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

So etwas wie

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

Damit können Sie nur zum Beispiel hat

assert(n == true);

und erhalten Sie die Quelldateinamen und die Zeilennummer des Problems Ihre Log ausgedruckt, wenn n falsch ist.

Wenn Sie einen normalen Funktionsaufruf verwenden wie

void assert(bool val);

anstelle des Makros, alles, was Sie bekommen können, ist Ihre Zeilennummer der Assertion-Funktion in das Protokoll gedruckt, die nützlich wäre weniger.

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

Im Gegensatz zu der ‚bevorzugte‘ Template-Lösung in einem aktuellen Thread diskutiert, können Sie es als konstanten Ausdruck verwenden können:

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

Sie können #defines verwenden mit Debugging und Unit-Test-Szenarien zu helfen. Erstellen Sie zum Beispiel spezielle Protokollvarianten der Speicherfunktionen und eine besondere memlog_preinclude.h erstellen:

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

Kompilieren Sie Code verwenden:

gcc -Imemlog_preinclude.h ...

Ein Link in Ihrem memlog.o auf das endgültige Bild. Sie jetzt malloc steuern, etc, vielleicht zur Protokollierung oder Zuordnungsfehler für Komponententests zu simulieren.

Ich verwende Makros leicht Ausnahmen definieren:

DEF_EXCEPTION(RessourceNotFound, "Ressource not found")

Dabei gilt DEF_EXCEPTION ist

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

Compiler können Sie Ihre Anfrage an Inline ablehnen.

Makros werden immer ihren Platz haben.

Etwas, das ich nützlich finden ist #define DEBUG für Debug-Tracing - Sie können es 1 verlassen kann, während ein Problem Debuggen (oder es sogar während des gesamten Entwicklungszyklus einwirken lassen), dann schalten Sie ihn aus, wenn es Zeit ist, zu versenden.

Wenn Sie eine Entscheidung zum Zeitpunkt der Kompilierung machen über Compiler / O / Hardware spezifisches Verhalten.

Es ermöglicht Ihnen, Ihre Schnittstelle zu Comppiler / O / Hardware Besonderheiten zu machen.

#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

In meinem letzten Job, ich arbeite an einem Virenscanner. Um was für mich einfacher zu debuggen, hatte ich viele Protokollierung überall stecken, aber in einer hohen Nachfrage App so, die Kosten eines Funktionsaufrufs ist einfach zu teuer. So kam ich mit diesem kleinen Makro auf, die noch erlaubten mir die Debug-Protokollierung auf einer Release-Version an einer Kunden-Website zu ermöglichen, ohne die Kosten für einen Funktionsaufruf würde den Debug-Flag überprüfen und zurück nur, ohne etwas anmelden, oder wenn aktiviert würde die Protokollierung tun ... 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 Log-Funktionen, das war ein gutes Beispiel für ein Makro wie folgt aus.

Davor, habe ich ein Makro in einer Hochsicherheitsanwendung, die dem Benutzer zu sagen brauchte, dass sie nicht den richtigen Zugang haben, und es würde ihnen sagen, welche Flagge sie benötigt werden.

Das Makro (s) wie folgt definiert:

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

Dann könnten wir nur die Kontrollen streuen ganze UI, und es würde Ihnen sagen, welche die Aktion, die Sie zu tun versuchten auszuführen wurden Rollen dürfen, wenn Sie nicht bereits diese Rolle haben. Der Grund für zwei von ihnen war ein Wert in einigen Orten zurückzukehren, und Rückkehr aus einer Leere Funktion in andere ...

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, das ist, wie ich sie benutzt habe, und ich bin nicht sicher, wie dies mit Vorlagen ... Abgesehen davon hätte geholfen werden können, versuche ich, sie zu vermeiden, es sei denn wirklich notwendig ist.

Noch ein anderes foreach Makros. T: Typ, c: Behälter, 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)

Usage (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 zur Verfügung: Google "BOOST_FOREACH"

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

Vielleicht ist die greates Verwendung von Makros in plattformunabhängige Entwicklung. Denken Sie über Fälle von Typ-Inkonsistenz - mit Makros Sie einfach unterschiedliche Header-Dateien verwenden können - 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

Viel lesbar als es auf andere Weise, meiner Meinung nach der Umsetzung.

Es scheint VA_ARGS hat nur indirekt bisher erwähnt worden:

Wenn generisch C ++ 03 Code zu schreiben, und Sie benötigen eine variable Anzahl von (generic) Parametern, können Sie einen Makro anstelle einer Vorlage 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 ist die Namensprüfung / throw auch in die hypothetische get_op_from_name Funktion eingebaut werden könnte. Dies ist nur ein Beispiel. Es könnten auch andere generische Code sein, den VA_ARGS Anruf umgibt.

Sobald wir variadische Vorlagen mit C ++ 11, können wir diese „richtig“ lösen mit einer Vorlage.

Ich denke, dieser Trick ist eine clevere Nutzung des Vorprozessors, die nicht mit einer Funktion emuliert werden:

#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 wie folgt verwendet werden:

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

Sie können auch einen RELEASE_ONLY Makro definieren.

Sie können Konstanten auf der Compiler-Befehlszeile #define die -D oder /D Option. Dies ist oft nützlich, wenn die gleiche Software für mehrere Plattformen Cross-Kompilierung, weil Sie können für jede Plattform, um Ihre Makefiles steuern, welche Konstanten definiert.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top