it-swarm.com.de

Wann sind C++ - Makros von Vorteil?

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

Das folgende Makro:

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

ist dem typ safe in keinster weise überlegen:

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

Aber Makros haben ihren Platz, bitte listen Sie die Verwendungen auf, die Sie für Makros finden, auf die Sie nicht ohne den Präprozessor verzichten können.

Bitte geben Sie für jeden Anwendungsfall eine eigene Antwort an, damit über ihn abgestimmt werden kann. Wenn Sie wissen, wie Sie eine der Antworten erreichen können, ohne dass der Präprozessor darauf hinweist, wie in den Kommentaren dieser Antwort vermerkt ist.

157
Motti

Als Wrapper für Debug-Funktionen, um Dinge wie __FILE__, __LINE__ usw. automatisch zu übergeben:

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

Methoden müssen immer vollständigen, kompilierbaren 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 wie folgt:

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

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

88
jdmichal

Header-Datei-Guards erfordern Makros.

Gibt es andere Bereiche, in denen Makros erforderlich sind? Nicht viele (falls vorhanden).

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 erfassen. 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 stecken. Anstatt jedes Mal die vollständigen catch-Blöcke auszutippen, gebe ich einfach Folgendes ein:

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 Präprozessor-Makros __FILE__ und __LINE__.

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

59
Kevin

Meistens:

  1. Fügen Sie Wachen hinzu
  2. Bedingte Kompilierung
  3. Reporting (vordefinierte Makros wie __LINE__ und __FILE__)
  4. (selten) Duplizieren sich wiederholender Codemuster.
  5. Im Code Ihres Mitbewerbers.
51
David Thornley

Innerhalb bedingter Kompilierung, um Unterschiede 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
50
Andrew Stein

Wenn Sie aus einem Ausdruck eine Zeichenfolge erstellen möchten, ist assert das beste Beispiel (#x wandelt den Wert von x in eine Zeichenfolge).

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

Zeichenfolgenkonstanten sind manchmal besser als Makros definiert, da Sie mit Zeichenfolgenlitalen mehr erreichen können als mit einem const char *.

z.B. 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 ein const char * verwendet würde, müsste eine Art String-Klasse 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);
28
Motti

Wenn Sie den Programmablauf ändern möchten (return, break und continue), verhält sich der Code in einer Funktion anders als der Code, der tatsächlich in der Funktion enthalten ist.

#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.
24
Motti

Das Offensichtliche umfasst Wachen

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif
20
Kena

Sie können einen Kurzschluss von Funktionsaufrufargumenten nicht mit einem regulären Funktionsaufruf durchfü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
17

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

16
Joe

Nehmen wir an, wir ignorieren offensichtliche Dinge wie Kopfschützer.

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)

so können Sie dies kodieren:

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

Und kann Meldungen erzeugen 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 (d. H. Die Werte werden automatisch nebeneinander mit ihren Variablennamen generiert).

In anderen Fällen benötigen Sie __FILE__ und/oder __LINE__ eines Codes, um beispielsweise Debug-Informationen zu generieren. Folgendes 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 erzeugt Meldungen 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. Generatoren und Setter für eine Eigenschaft (dies ist in bestimmten Fällen der Fall).

In anderen Fällen generieren Sie Code, der nicht kompiliert wird, wenn er von einer Funktion verwendet wird, 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 gesehen, dass diese Art von Code einmal richtig verwendet wurde)

Last but not least der 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 wird von der Boost-Homepage kopiert/eingefügt)

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

Makros sind daher immer nützlich, da sie außerhalb der normalen Compiler-Regeln liegen. Aber ich finde, dass die meisten, die ich sehe, tatsächlich Reste von C-Code sind, der niemals in korrektes C++ übersetzt wird.

16
paercebal

Den C-Präprozessor zu fürchten ist wie die Glühbirnen zu fürchten, nur weil wir Leuchtstofflampen bekommen. Ja, der erstere kann sein Programmierzeit} ineffizient. Ja, Sie können (buchstäblich) von ihnen verbrannt werden. Sie können jedoch die Arbeit erledigen, wenn Sie richtig damit umgehen. 

Wenn Sie eingebettete Systeme programmieren, ist C die einzige Option neben dem Formularassembler. Nachdem Sie mit C++ auf dem Desktop programmiert und dann zu kleineren, eingebetteten Zielen gewechselt haben, lernen Sie, sich keine Sorgen mehr über "Inelegancies" so vieler bloßer C-Funktionen zu machen (einschließlich Makros) Eigenschaften.

Alexander Stepanov sagt :

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

14
VictorH

Wir verwenden die __FILE__- und __LINE__-Makros zu Diagnosezwecken beim Auslösen, Erfassen und Protokollieren von Ausnahmebedingungen, zusammen mit automatisierten Protokolldateiscannern in unserer QA-Infrastruktur.

Beispielsweise kann ein auslösendes Makro OUR_OWN_THROW mit 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 InvalidOperationException-Ausnahme mit der Beschreibung als Konstruktorparameter aus, schreibt jedoch auch eine Nachricht in eine Protokolldatei, die aus dem Dateinamen und der Zeilennummer, an der der Wurf aufgetreten ist, und seiner textuellen Beschreibung besteht. Die geworfene Ausnahme erhält eine ID, die auch protokolliert wird. Wenn die Ausnahme an einer anderen Stelle im Code abgefangen wird, wird sie als solche markiert und die Protokolldatei zeigt dann an, dass die betreffende Ausnahme behandelt wurde und daher wahrscheinlich nicht die Ursache für einen Absturz ist, der möglicherweise später protokolliert wird. Unbehandelte Ausnahmen können von unserer automatisierten QS-Infrastruktur leicht aufgegriffen werden.

9
Johann Gerell

Code-Wiederholung.

Werfen Sie einen Blick auf die preprocessor library , eine Art Meta-Meta-Programmierung. In Thema-> Motivation finden Sie ein gutes Beispiel.

9
Ruggero Turra

Einige sehr fortgeschrittene und nützliche Dinge können immer noch mithilfe von Präprozessoren (Makros) erstellt werden, die Sie unter Verwendung der c ++ "Sprachkonstrukte", einschließlich Vorlagen, niemals tun würden.

Beispiele:

Etwas sowohl als C-Kennung als auch als Zeichenfolge erstellen

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

Boost-Präprozessor-Metaprogrammierung

8
Suma

Ich verwende gelegentlich Makros, damit ich Informationen an einer Stelle definieren kann, sie aber in verschiedenen Teilen des Codes auf unterschiedliche Weise verwenden kann. Es ist nur ein bisschen 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 ein öffentliches Enum 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 aufzufüllen:

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

#include "field_list.h"
7
Andrew Johnson

So etwas wie

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

Damit kannst du es nur zum Beispiel haben

assert(n == true);

und lassen Sie den Quelldateinamen und die Zeilennummer des Problems in Ihrem Protokoll ausgeben, wenn n falsch ist.

Wenn Sie einen normalen Funktionsaufruf verwenden, z

void assert(bool val);

anstelle des Makros können Sie nur die Zeilennummer Ihrer Assert-Funktion erhalten, die im Protokoll gedruckt wird. Dies wäre weniger nützlich.

6
Keshi

Eine gängige Anwendung ist das Erkennen der Kompilierungsumgebung. Für die plattformübergreifende Entwicklung können Sie beispielsweise einen Satz von 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 vorliegen

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

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

6
Doug T.
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

Im Gegensatz zur "bevorzugten" Vorlagenlösung, die in einem aktuellen Thread diskutiert wird, können Sie sie als konstanten Ausdruck verwenden:

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

Ich benutze Makros, um Ausnahmen einfach zu definieren:

DEF_EXCEPTION(RessourceNotFound, "Ressource not found")

wo ist DEF_EXCEPTION

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

Sie können #defines verwenden, um bei Debugging- und Komponententestszenarien 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 Ihrem memlog.o zum endgültigen Bild. Sie kontrollieren jetzt malloc usw., möglicherweise zu Protokollierungszwecken oder zur Simulation von Zuordnungsfehlern für Komponententests.

3
Andrew Johnson

Compiler können Ihre Inline-Anfrage ablehnen.

Makros haben immer ihren Platz.

Ich halte #define DEBUG für die Debug-Ablaufverfolgung für nützlich. Sie können es 1 beim Debuggen eines Problems belassen (oder sogar während des gesamten Entwicklungszyklus), und es dann ausschalten, wenn es Zeit für den Versand ist.

2
unwieldy

Wenn Sie sich zur Compilierzeit über Compiler/OS/Hardware-spezifisches Verhalten entscheiden.

Damit können Sie Ihre Schnittstelle an spezifische Funktionen von Comppiler/OS/Hardware anpassen.

#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
2
Martin York

Es scheint, dass VA_ARGS bisher nur indirekt erwähnt wurde:

Wenn Sie generischen C++ 03-Code schreiben und eine variable Anzahl von (generischen) Parametern benötigen, können Sie ein 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 kann der Name check/throw auch in die hypothetische get_op_from_name-Funktion integriert werden. Dies ist nur ein Beispiel. Möglicherweise gibt es einen anderen generischen Code, der den VA_ARGS-Aufruf umgibt.

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

2
Martin Ba

Die Verwendung von Makros ist in erster Linie eine plattformunabhängige Entwicklung. Denken Sie an Fälle von Typinkonsistenz - bei Makros können Sie einfach verschiedene Header-Dateien verwenden, wie: -- WIN_TYPES.H

typedef ...some struct

--POSIX_TYPES.h

typedef ...some another struct

--programm.h

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

#include TYPES_H

Viel lesbarer als andere Implementierung, meiner Meinung nach.

2
rkellerm

In meinem letzten Job arbeitete ich an einem Virenscanner. Um mir das Debuggen zu erleichtern, hatte ich eine Menge Protokollierung überall, aber bei einer solchen App mit hoher Nachfrage sind die Kosten eines Funktionsaufrufs einfach zu teuer. So kam ich zu diesem kleinen Makro, das mir immer noch erlaubte, die Debug-Protokollierung für eine Release-Version bei einem Kunden vor Ort zu aktivieren, ohne dass die Kosten eines Funktionsaufrufs das Debug-Flag prü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 solches Makro.

Vorher habe ich ein Makro in einer Hochsicherheitsanwendung verwendet, das dem Benutzer mitteilen musste, dass er nicht den richtigen Zugriff hatte, und er würde ihnen sagen, welches Flag er benötigt.

Die Makros 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 verteilen und würden Ihnen sagen, welche Rollen die Aktion ausführen dürfen, die Sie versucht haben, wenn Sie diese Rolle nicht bereits hatten. Der Grund für zwei von ihnen war, an einigen Stellen einen Wert zurückzugeben und an anderen Stellen 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 benutzt, und ich bin mir nicht sicher, wie man mit Vorlagen hätte helfen können ... Ansonsten versuche ich, sie zu vermeiden, es sei denn, es ist wirklich notwendig.

2
LarryF

Noch ein weiteres Foreach-Makros. 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 zur Verfügung: Bedingte Liebe: FOREACH Redux (Eric Niebler) http://www.artima.com/cppsource/foreach.html

2
Notinlist

Ich habe den Präprozessor verwendet, um Festkommazahlen aus Gleitkommawerten zu berechnen, die in eingebetteten Systemen verwendet werden, die im kompilierten Code keinen Gleitkommawert verwenden können. Es ist praktisch, wenn Sie alle Ihre Berechnungen in Einheiten der realen Welt haben und nicht in festen Punkten darüber nachdenken müssen.

Beispiel:

// TICKS_PER_UNIT is defined in floating point to allow the conversions to compute during compile-time.
#define TICKS_PER_UNIT  1024.0


// NOTE: The TICKS_PER_x_MS will produce constants in the preprocessor.  The (long) cast will
//       guarantee there are no floating point values in the embedded code and will produce a warning
//       if the constant is larger than the data type being stored to.
//       Adding 0.5 sec to the calculation forces rounding instead of truncation.
#define TICKS_PER_1_MS( ms ) (long)( ( ( ms * TICKS_PER_UNIT ) / 1000 ) + 0.5 )
1
dwj

Wenn Sie eine Liste von Feldern haben, die für eine Reihe von Dingen verwendet werden, z. Definieren einer Struktur, Serialisieren dieser Struktur in ein oder aus einem Binärformat, Durchführen von Datenbankeinfügungen usw. Sie können (rekursiv!) den Präprozessor verwenden, um zu vermeiden, dass Ihre Feldliste wiederholt wird.

Das ist zugegebenermaßen abscheulich. Aber vielleicht manchmal besser, als eine lange Liste von Feldern an mehreren Stellen zu aktualisieren? Ich habe diese Technik genau einmal angewendet und es war sehr hilfreich.

Natürlich wird die gleiche Grundidee in Sprachen mit angemessener Reflexion ausgiebig verwendet. Sie müssen nur die Klasse prüfen und nacheinander jedes Feld bearbeiten. Im C-Präprozessor zu tun, ist fragil, unleserlich und nicht immer portabel. Ich erwähne es mit einiger Besorgnis. Trotzdem ist es hier ...

(EDIT: Ich sehe jetzt, dass dies dem von @Andrew Johnson am 18.9. Ähnlich ist; die Idee, die gleiche Datei rekursiv einzufügen, führt die Idee jedoch etwas weiter.)

// file foo.h, defines class Foo and various members on it without ever repeating the
// list of fields.

#if defined( FIELD_LIST )
   // here's the actual list of fields in the class.  If FIELD_LIST is defined, we're at
   // the 3rd level of inclusion and somebody wants to actually use the field list.  In order
   // to do so, they will have defined the macros STRING and INT before including us.
   STRING( fooString )
   INT( barInt )   
#else // defined( FIELD_LIST )

#if !defined(FOO_H)
#define FOO_H

#define DEFINE_STRUCT
// recursively include this same file to define class Foo
#include "foo.h"
#undef DEFINE_STRUCT

#define DEFINE_CLEAR
// recursively include this same file to define method Foo::clear
#include "foo.h"
#undef DEFINE_CLEAR

// etc ... many more interesting examples like serialization

#else // defined(FOO_H)
// from here on, we know that FOO_H was defined, in other words we're at the second level of
// recursive inclusion, and the file is being used to make some particular
// use of the field list, for example defining the class or a single method of it

#if defined( DEFINE_STRUCT )
#define STRING(a)  std::string a;
#define INT(a)     long a;
   class Foo
   {
      public:
#define FIELD_LIST
// recursively include the same file (for the third time!) to get fields
// This is going to translate into:
//    std::string fooString;
//    int barInt;
#include "foo.h"
#endif

      void clear();
   };
#undef STRING
#undef INT
#endif // defined(DEFINE_STRUCT)


#if defined( DEFINE_ZERO )
#define STRING(a) a = "";
#define INT(a) a = 0;
#define FIELD_LIST
   void Foo::clear()
   {
// recursively include the same file (for the third time!) to get fields.
// This is going to translate into:
//    fooString="";
//    barInt=0;
#include "foo.h"
#undef STRING
#undef int
   }
#endif // defined( DEFINE_ZERO )

// etc...


#endif // end else clause for defined( FOO_H )

#endif // end else clause for defined( FIELD_LIST )
1
Eric

Sie können #define-Konstanten in der Compiler-Befehlszeile mit der Option -D oder /D verwenden. Dies ist häufig hilfreich, wenn Sie die gleiche Software für mehrere Plattformen überkompilieren, da Sie mit Ihren Makefiles steuern können, welche Konstanten für jede Plattform definiert sind.

1
bk1e

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 kannst du es so benutzen:

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

Sie können auch ein RELEASE_ONLY-Makro definieren.

1
Mathieu Pagé

Sie können die zusätzliche Protokollierung in einem Debug-Build aktivieren und für einen Release-Build deaktivieren, ohne dass eine boolesche Überprüfung erforderlich ist. Also statt:

void Log::trace(const char *pszMsg) {
    if (!bDebugBuild) {
        return;
    }
    // Do the logging
}

...

log.trace("Inside MyFunction");

Du kannst haben:

#ifdef _DEBUG
#define LOG_TRACE log.trace
#else
#define LOG_TRACE void
#endif

...

LOG_TRACE("Inside MyFunction");

Wenn _DEBUG nicht definiert ist, wird überhaupt kein Code generiert. Ihr Programm wird schneller ausgeführt und der Text für die Traceprotokollierung wird nicht in Ihre ausführbare Datei übersetzt.

0
Ates Goral

Makros sind nützlich, um die Syntax von switch-Anweisungen zu simulieren:

switch(x) {
case val1: do_stuff(); break;
case val2: do_other_stuff();
case val3: yet_more_stuff();
default:   something_else();
}

für nicht ganzzahlige Werttypen. In dieser Frage: 

Strings in switch-Anweisungen verwenden - wo stehen wir mit C++ 17?

hier finden Sie Antworten, die einige Ansätze mit Lambdas nahe legen, aber leider sind es Makros, die uns am nächsten kommen:

SWITCH(x)
CASE val1  do_stuff(); break;
CASE val2  do_other_stuff();
CASE val3  yet_more_stuff();
DEFAULT    something_else();
END
0
einpoklum
#define COLUMNS(A,B) [(B) - (A) + 1]

struct 
{
    char firstName COLUMNS(  1,  30);
    char lastName  COLUMNS( 31,  60);
    char address1  COLUMNS( 61,  90);
    char address2  COLUMNS( 91, 120);
    char city      COLUMNS(121, 150);
};
0
EvilTeach

Können Sie dies als Inline-Funktion implementieren?

#define my_free(x) do { free(x); x = NULL; } while (0)
0
mbac32768

Sie benötigen Makros für Ressourcenkennungen in Visual Studio, da der Ressourcencompiler sie nur versteht (d. H. Er funktioniert nicht mit const oder enum).

0
Harold Ekstrom