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.
c++
c-preprocessor
Motti
quelle
quelle
Antworten:
Als Wrapper für Debug - Funktionen, übergeben , um automatisch Dinge wie
__FILE__
,__LINE__
etc:quelle
__FILE__
und__LINE__
auch den Präprozessor. Die Verwendung in Ihrem Code ist wie ein Infektionsvektor für den Präprozessor.Methoden müssen immer vollständiger, kompilierbarer Code sein; Makros können Codefragmente sein. So können Sie ein foreach-Makro definieren:
Und benutze es so:
Seit C ++ 11 wird dies durch die bereichsbasierte for-Schleife ersetzt .
quelle
for_each
war 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_FOREACH
anstelle 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_each
könnte es wieder der richtige Weg sein.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:
Ich muss diese Fänge in jede Wrapper-Funktion einfügen. Anstatt jedes Mal die vollständigen Fangblöcke auszutippen, tippe ich einfach:
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.
quelle
#pragma once
diese Tage, daher bezweifle ich, dass Wachen wirklich notwendig sind#pragma once
bricht auf vielen gängigen Build-Systemen.void handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }
. Und auf der Funktionsseite:void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }
Meist:
__LINE__
und__FILE__
)quelle
Innerhalb der bedingten Kompilierung, um Probleme mit Unterschieden zwischen Compilern zu überwinden:
quelle
close
Funktionen 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.#ifdef WE_ARE_ON_WIN32
PLZ :)Wenn Sie aus einem Ausdruck eine Zeichenfolge machen möchten, ist das beste Beispiel dafür
assert
(#x
wandelt den Wert vonx
in eine Zeichenfolge um).quelle
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 .
Wenn a
const char *
verwendet würde, müsste eine Art Zeichenfolgenklasse verwendet werden, um die Verkettung zur Laufzeit durchzuführen:quelle
Wenn Sie den Programmablauf ändern (
return
,break
undcontinue
) Code in einer Funktion verhält sich anders als Code, der tatsächlich in der Funktion inlined wird.quelle
-1
oder zurückgegeben wirdNULL
. Ein Makro kann dort also den Boilerplate-Code erheblich reduzieren.Zu den offensichtlichen gehören Wachen
quelle
Sie können Funktionsaufrufargumente nicht mit einem regulären Funktionsaufruf kurzschließen. Beispielsweise:
quelle
Nehmen wir an, wir ignorieren offensichtliche Dinge wie Header Guards.
Manchmal möchten Sie Code generieren, der vom Precompiler kopiert / eingefügt werden muss:
Damit können Sie Folgendes codieren:
Und kann Nachrichten generieren wie:
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 ++:
Wie beim folgenden Code:
Es generiert Nachrichten wie:
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.
Welches kann als verwendet werden
(Trotzdem habe ich diese Art von Code nur einmal richtig gesehen )
Zu guter Letzt das berühmte
boost::foreach
!!!(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.
quelle
#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.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.
quelle
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 :
quelle
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_THROW
mit Ausnahmetyp- und Konstruktorparametern für diese Ausnahme verwendet werden, einschließlich einer Textbeschreibung. So was:Dieses Makro löst natürlich die
InvalidOperationException
Ausnahme 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.quelle
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.
quelle
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
quelle
stdio.h
undsal.h
legen Sie ihn ab,vc12
um ihn besser zu verstehen.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":
Dann kann für eine öffentliche Aufzählung definiert werden, dass nur der Name verwendet wird:
In einer privaten Init-Funktion können alle Felder verwendet werden, um eine Tabelle mit den Daten zu füllen:
quelle
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
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.
quelle
Etwas wie
So dass Sie nur zum Beispiel haben können
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
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.
quelle
<cassert>
dasassert()
Makro bereitgestellt werden, das die Datei- / Zeilen- / Funktionsinformationen ausgibt? (in allen Implementierungen, die ich sowieso gesehen habe)Im Gegensatz zu der in einem aktuellen Thread beschriebenen "bevorzugten" Vorlagenlösung können Sie sie als konstanten Ausdruck verwenden:
quelle
template<typename T, std::size_t size> constexpr std::size_t array_size(T const (&)[size]) { return size; }
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:
Kompilieren Sie Ihren Code mit:
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.
quelle
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.
quelle
Ich benutze Makros, um Ausnahmen einfach zu definieren:
wo DEF_EXCEPTION ist
quelle
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.
quelle
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:
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:
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 ...
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.
quelle
Noch ein foreach-Makro. T: Typ, c: Container, i: Iterator
Verwendung (Konzept zeigt, nicht real):
Bessere Implementierungen verfügbar: Google "BOOST_FOREACH"
Gute Artikel verfügbar: Bedingte Liebe: FOREACH Redux (Eric Niebler) http://www.artima.com/cppsource/foreach.html
quelle
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
--POSIX_TYPES.h
--program.h
Meiner Meinung nach viel lesbarer als auf andere Weise umzusetzen.
quelle
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.
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 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.
quelle
Ich denke, dieser Trick ist eine clevere Verwendung des Präprozessors, die mit einer Funktion nicht emuliert werden kann:
Dann können Sie es so verwenden:
Sie können auch ein RELEASE_ONLY-Makro definieren.
quelle
Sie können
#define
Konstanten in der Compiler-Befehlszeile mit der Option-D
oder/D
festlegen. 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.quelle