Wann sind C ++ vorteilhaft Makros? [geschlossen]
-
01-07-2019 - |
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.
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:
- Neue Wächter
- Bedingtes Kompilieren
- Berichte (vordefinierte Makros wie
__LINE__
und__FILE__
) - (selten) Duplizieren wiederholende Codemuster.
- 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
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.