Abgesehen davon , ist das Makro ein intund Sie constexpr unsignedein unsigned, gibt es wichtige Unterschiede und Makros nur haben einen Vorteil.
Umfang
Ein Makro wird vom Präprozessor definiert und bei jedem Auftreten einfach in den Code eingesetzt. Der Präprozessor ist dumm und versteht die C ++ - Syntax oder -Semantik nicht. Makros ignorieren Bereiche wie Namespaces, Klassen oder Funktionsblöcke, sodass Sie keinen Namen für andere Elemente in einer Quelldatei verwenden können. Dies gilt nicht für eine Konstante, die als richtige C ++ - Variable definiert ist:
Es ist in Ordnung, eine Mitgliedsvariable aufzurufen, max_heightda sie ein Klassenmitglied ist und daher einen anderen Bereich hat und sich von dem im Namespace-Bereich unterscheidet. Wenn Sie versuchen würden, den Namen MAX_HEIGHTfür das Mitglied wiederzuverwenden , würde der Präprozessor ihn in diesen Unsinn ändern, der nicht kompiliert werden würde:
classWindow {// ...int720;
};
Aus diesem Grund müssen Sie Makros angeben, UGLY_SHOUTY_NAMESum sicherzustellen, dass sie hervorstechen, und Sie können vorsichtig sein, wenn Sie sie benennen, um Konflikte zu vermeiden. Wenn Sie Makros nicht unnötig verwenden, müssen Sie sich darüber keine Sorgen machen (und müssen nicht lesenSHOUTY_NAMES ).
Wenn Sie nur eine Konstante in einer Funktion haben möchten, können Sie dies nicht mit einem Makro tun, da der Präprozessor nicht weiß, was eine Funktion ist oder was es bedeutet, sich in ihr zu befinden. Um ein Makro nur auf einen bestimmten Teil einer Datei zu beschränken, müssen Sie #undefes erneut ausführen:
Eine constexpr-Variable ist eine Variable also tatsächlich im Programm vorhanden ist, und Sie können normale C ++ - Dinge tun, z. B. ihre Adresse nehmen und einen Verweis darauf binden.
Das Problem ist, dass MAX_HEIGHTes sich nicht um eine Variable handelt, sodass der Aufruf std::maxeines temporären intElements vom Compiler erstellt werden muss. Die Referenz, die von zurückgegeben wird, std::maxverweist dann möglicherweise auf die temporäre Referenz , die nach dem Ende dieser Anweisung nicht mehr vorhanden ist, sodass return hauf ungültigen Speicher zugegriffen wird.
Dieses Problem besteht bei einer richtigen Variablen einfach nicht, da sie einen festen Speicherort hat, der nicht verschwindet:
(In der Praxis würden Sie wahrscheinlich int hnicht erklären , const int& haber das Problem kann in subtileren Kontexten auftreten.)
Präprozessorbedingungen
Die einzige Zeit, um ein Makro zu bevorzugen, ist, wenn Sie möchten, dass sein Wert vom Präprozessor verstanden wird, um ihn unter #ifBedingungen zu verwenden, z
Sie konnten hier keine Variable verwenden, da der Präprozessor nicht versteht, wie Variablen namentlich referenziert werden. Es versteht nur grundlegende sehr grundlegende Dinge wie Makroerweiterung und Direktiven, die mit #(wie #includeund #defineund #if) beginnen.
Wenn Sie eine Konstante wünschen, die vom Präprozessor verstanden werden kann, sollten Sie den Präprozessor verwenden, um sie zu definieren. Wenn Sie eine Konstante für normalen C ++ - Code wünschen, verwenden Sie normalen C ++ - Code.
Das obige Beispiel soll nur eine Präprozessorbedingung demonstrieren, aber selbst dieser Code könnte die Verwendung des Präprozessors vermeiden:
using height_type = std::conditional_t<max_height < 256, unsignedchar, unsignedint>;
Eine constexprVariable muss erst Speicher belegen, wenn ihre Adresse (ein Zeiger / eine Referenz) verwendet wurde. Andernfalls kann es vollständig optimiert werden (und ich denke, es könnte Standardese geben, das dies garantiert). Ich möchte dies betonen, damit die Leute den alten, minderwertigen " enumHack" nicht aus einer fehlgeleiteten Vorstellung heraus weiter verwenden, dass ein Trivial constexpr, das keinen Speicher benötigt, dennoch einige davon besetzt.
underscore_d
3
Ihr Abschnitt "Ein realer Speicherort" ist falsch: 1. Sie geben nach Wert (int) zurück, sodass eine Kopie erstellt wird. Das temporäre ist kein Problem. 2. Wenn Sie als Referenz (int &) zurückgekehrt int heightwären, wäre dies genauso ein Problem wie das Makro, da sein Umfang an die Funktion gebunden ist, im Wesentlichen auch vorübergehend. 3. Der obige Kommentar "const int & h verlängert die Lebensdauer des temporären" ist korrekt.
PoweredByRice
4
@underscore_d true, aber das ändert nichts am Argument. Die Variable benötigt keinen Speicher, es sei denn, sie wird odr verwendet. Der Punkt ist, dass, wenn eine echte Variable mit Speicher benötigt wird, die Variable constexpr das Richtige tut.
Jonathan Wakely
1
@PoweredByRice 1. Das Problem hat nichts mit dem Rückgabewert von zu tun. limitDas Problem ist der Rückgabewert von std::max. 2. Ja, deshalb wird keine Referenz zurückgegeben. 3. falsch, siehe den coliru Link oben.
Jonathan Wakely
3
@PoweredByRice seufz, du musst mir wirklich nicht erklären, wie C ++ funktioniert. Wenn Sie den Wert haben const int& h = max(x, y);und maxum diesen zurückgeben, verlängert sich die Lebensdauer des Rückgabewerts. Nicht nach dem Rückgabetyp, sondern nach dem, an den const int&es gebunden ist. Was ich geschrieben habe, ist richtig.
Jonathan Wakely
11
Im Allgemeinen sollten constexprSie Makros verwenden, wann immer Sie möchten, und nur dann, wenn keine andere Lösung möglich ist.
Begründung:
Makros sind eine einfache Ersetzung im Code und führen aus diesem Grund häufig zu Konflikten (z. B. Windows.h- maxMakro vs std::max). Darüber hinaus kann ein funktionierendes Makro leicht auf andere Weise verwendet werden, was dann seltsame Kompilierungsfehler auslösen kann. (z.BQ_PROPERTY für Strukturelemente verwendet)
Aufgrund all dieser Unsicherheiten ist es ein guter Codestil, Makros zu vermeiden, genau wie Sie normalerweise Gotos vermeiden würden.
constexpr ist semantisch definiert und erzeugt daher typischerweise weit weniger Probleme.
In welchem Fall ist die Verwendung eines Makros unvermeidlich?
Tom Dorone
3
Bedingte Kompilierung unter Verwendung von #ifDingen, für die der Präprozessor tatsächlich nützlich ist. Das Definieren einer Konstante ist nicht eines der Dinge, für die der Präprozessor nützlich ist, es sei denn, diese Konstante muss ein Makro sein, da sie unter Präprozessorbedingungen verwendet wird #if. Wenn die Konstante für die Verwendung in normalem C ++ - Code (keine Präprozessoranweisungen) vorgesehen ist, verwenden Sie eine normale C ++ - Variable, kein Präprozessor-Makro.
Jonathan Wakely
Abgesehen von der Verwendung variadischer Makros, meistens der Verwendung von Makros für Compiler-Switches, ist es jedoch eine gute Idee, aktuelle Makroanweisungen (wie bedingte Switches, String-Literal-Switches) zu ersetzen, die sich mit echten Code-Anweisungen mit constexpr befassen.
Ich würde sagen, dass Compiler-Switches auch keine gute Idee sind. Ich verstehe jedoch voll und ganz, dass es manchmal benötigt wird (auch Makros), insbesondere wenn es um plattformübergreifenden oder eingebetteten Code geht. Um Ihre Frage zu beantworten: Wenn Sie bereits mit Präprozessor zu tun haben, würde ich Makros verwenden, um klar und intuitiv zu halten, was Präprozessor und was Kompilierungszeit ist. Ich würde auch vorschlagen, stark zu kommentieren und die Verwendung so kurz und lokal wie möglich zu gestalten (vermeiden Sie Makros, die sich um oder 100 Zeilen #if verbreiten). Vielleicht ist die Ausnahme der typische # ifndef-Schutz (Standard für #pragma einmal), der gut verstanden wird.
Adrian Maire
3
Tolle Antwort von Jonathon Wakely . Ich würde Ihnen auch raten, sich die Antwort von jogojapan anzusehen, um herauszufinden , was der Unterschied zwischen constund istconstexpr bevor Sie überhaupt über die Verwendung von Makros nachdenken.
Makros sind dumm, aber auf gute Weise. Angeblich sind sie heutzutage eine Build-Hilfe, wenn Sie möchten, dass ganz bestimmte Teile Ihres Codes nur kompiliert werden, wenn bestimmte Build-Parameter "definiert" werden. Normalerweise bedeutet dies nur, dass Sie Ihren Makronamen verwenden, oder besser noch, nennen wir ihn a Triggerund fügen Dinge hinzu wie /D:Trigger:-DTrigger etc. zu den Build - Tools verwendet werden.
Obwohl es viele verschiedene Verwendungszwecke für Makros gibt, sind dies die beiden, die ich am häufigsten sehe und die keine schlechten / veralteten Praktiken sind:
Hardware- und plattformspezifische Codeabschnitte
Erhöhte Ausführlichkeit baut auf
Während Sie im Fall des OP das gleiche Ziel erreichen können, ein int mit constexproder a zu definieren MACRO, ist es unwahrscheinlich, dass sich die beiden bei Verwendung moderner Konventionen überschneiden. Hier sind einige allgemeine Makronutzungen, die noch nicht eingestellt wurden.
#if defined VERBOSE || defined DEBUG || defined MSG_ALL// Verbose message-handling code here#endif
Nehmen wir als weiteres Beispiel für die Verwendung von Makros an, dass Sie eine bevorstehende Hardware veröffentlichen müssen oder eine bestimmte Generation davon, die einige knifflige Problemumgehungen enthält, die die anderen nicht benötigen. Wir definieren dieses Makro als GEN_3_HW.
#if defined GEN_3_HW && defined _WIN64// Windows-only special handling for 64-bit upcoming hardware#elif defined GEN_3_HW && defined __APPLE__// Special handling for macs on the new hardware#elif !defined _WIN32 && !defined __linux__ && !defined __APPLE__ && !defined __ANDROID__ && !defined __unix__ && !defined __arm__// Greetings, Outlander! ;)#else// Generic handling#endif
Antworten:
Nein auf keinen Fall. Nicht einmal annähernd.
Abgesehen davon , ist das Makro ein
int
und Sieconstexpr unsigned
einunsigned
, gibt es wichtige Unterschiede und Makros nur haben einen Vorteil.Umfang
Ein Makro wird vom Präprozessor definiert und bei jedem Auftreten einfach in den Code eingesetzt. Der Präprozessor ist dumm und versteht die C ++ - Syntax oder -Semantik nicht. Makros ignorieren Bereiche wie Namespaces, Klassen oder Funktionsblöcke, sodass Sie keinen Namen für andere Elemente in einer Quelldatei verwenden können. Dies gilt nicht für eine Konstante, die als richtige C ++ - Variable definiert ist:
#define MAX_HEIGHT 720 constexpr int max_height = 720; class Window { // ... int max_height; };
Es ist in Ordnung, eine Mitgliedsvariable aufzurufen,
max_height
da sie ein Klassenmitglied ist und daher einen anderen Bereich hat und sich von dem im Namespace-Bereich unterscheidet. Wenn Sie versuchen würden, den NamenMAX_HEIGHT
für das Mitglied wiederzuverwenden , würde der Präprozessor ihn in diesen Unsinn ändern, der nicht kompiliert werden würde:class Window { // ... int 720; };
Aus diesem Grund müssen Sie Makros angeben,
UGLY_SHOUTY_NAMES
um sicherzustellen, dass sie hervorstechen, und Sie können vorsichtig sein, wenn Sie sie benennen, um Konflikte zu vermeiden. Wenn Sie Makros nicht unnötig verwenden, müssen Sie sich darüber keine Sorgen machen (und müssen nicht lesenSHOUTY_NAMES
).Wenn Sie nur eine Konstante in einer Funktion haben möchten, können Sie dies nicht mit einem Makro tun, da der Präprozessor nicht weiß, was eine Funktion ist oder was es bedeutet, sich in ihr zu befinden. Um ein Makro nur auf einen bestimmten Teil einer Datei zu beschränken, müssen Sie
#undef
es erneut ausführen:int limit(int height) { #define MAX_HEIGHT 720 return std::max(height, MAX_HEIGHT); #undef MAX_HEIGHT }
Vergleichen Sie mit dem weitaus vernünftigeren:
int limit(int height) { constexpr int max_height = 720; return std::max(height, max_height); }
Warum bevorzugen Sie das Makro?
Ein echter Speicherort
Eine constexpr-Variable ist eine Variable also tatsächlich im Programm vorhanden ist, und Sie können normale C ++ - Dinge tun, z. B. ihre Adresse nehmen und einen Verweis darauf binden.
Dieser Code hat ein undefiniertes Verhalten:
#define MAX_HEIGHT 720 int limit(int height) { const int& h = std::max(height, MAX_HEIGHT); // ... return h; }
Das Problem ist, dass
MAX_HEIGHT
es sich nicht um eine Variable handelt, sodass der Aufrufstd::max
eines temporärenint
Elements vom Compiler erstellt werden muss. Die Referenz, die von zurückgegeben wird,std::max
verweist dann möglicherweise auf die temporäre Referenz , die nach dem Ende dieser Anweisung nicht mehr vorhanden ist, sodassreturn h
auf ungültigen Speicher zugegriffen wird.Dieses Problem besteht bei einer richtigen Variablen einfach nicht, da sie einen festen Speicherort hat, der nicht verschwindet:
int limit(int height) { constexpr int max_height = 720; const int& h = std::max(height, max_height); // ... return h; }
(In der Praxis würden Sie wahrscheinlich
int h
nicht erklären ,const int& h
aber das Problem kann in subtileren Kontexten auftreten.)Präprozessorbedingungen
Die einzige Zeit, um ein Makro zu bevorzugen, ist, wenn Sie möchten, dass sein Wert vom Präprozessor verstanden wird, um ihn unter
#if
Bedingungen zu verwenden, z#define MAX_HEIGHT 720 #if MAX_HEIGHT < 256 using height_type = unsigned char; #else using height_type = unsigned int; #endif
Sie konnten hier keine Variable verwenden, da der Präprozessor nicht versteht, wie Variablen namentlich referenziert werden. Es versteht nur grundlegende sehr grundlegende Dinge wie Makroerweiterung und Direktiven, die mit
#
(wie#include
und#define
und#if
) beginnen.Wenn Sie eine Konstante wünschen, die vom Präprozessor verstanden werden kann, sollten Sie den Präprozessor verwenden, um sie zu definieren. Wenn Sie eine Konstante für normalen C ++ - Code wünschen, verwenden Sie normalen C ++ - Code.
Das obige Beispiel soll nur eine Präprozessorbedingung demonstrieren, aber selbst dieser Code könnte die Verwendung des Präprozessors vermeiden:
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
quelle
constexpr
Variable muss erst Speicher belegen, wenn ihre Adresse (ein Zeiger / eine Referenz) verwendet wurde. Andernfalls kann es vollständig optimiert werden (und ich denke, es könnte Standardese geben, das dies garantiert). Ich möchte dies betonen, damit die Leute den alten, minderwertigen "enum
Hack" nicht aus einer fehlgeleiteten Vorstellung heraus weiter verwenden, dass ein Trivialconstexpr
, das keinen Speicher benötigt, dennoch einige davon besetzt.int height
wären, wäre dies genauso ein Problem wie das Makro, da sein Umfang an die Funktion gebunden ist, im Wesentlichen auch vorübergehend. 3. Der obige Kommentar "const int & h verlängert die Lebensdauer des temporären" ist korrekt.limit
Das Problem ist der Rückgabewert vonstd::max
. 2. Ja, deshalb wird keine Referenz zurückgegeben. 3. falsch, siehe den coliru Link oben.const int& h = max(x, y);
undmax
um diesen zurückgeben, verlängert sich die Lebensdauer des Rückgabewerts. Nicht nach dem Rückgabetyp, sondern nach dem, an denconst int&
es gebunden ist. Was ich geschrieben habe, ist richtig.Im Allgemeinen sollten
constexpr
Sie Makros verwenden, wann immer Sie möchten, und nur dann, wenn keine andere Lösung möglich ist.Begründung:
Makros sind eine einfache Ersetzung im Code und führen aus diesem Grund häufig zu Konflikten (z. B. Windows.h-
max
Makro vsstd::max
). Darüber hinaus kann ein funktionierendes Makro leicht auf andere Weise verwendet werden, was dann seltsame Kompilierungsfehler auslösen kann. (z.BQ_PROPERTY
für Strukturelemente verwendet)Aufgrund all dieser Unsicherheiten ist es ein guter Codestil, Makros zu vermeiden, genau wie Sie normalerweise Gotos vermeiden würden.
constexpr
ist semantisch definiert und erzeugt daher typischerweise weit weniger Probleme.quelle
#if
Dingen, für die der Präprozessor tatsächlich nützlich ist. Das Definieren einer Konstante ist nicht eines der Dinge, für die der Präprozessor nützlich ist, es sei denn, diese Konstante muss ein Makro sein, da sie unter Präprozessorbedingungen verwendet wird#if
. Wenn die Konstante für die Verwendung in normalem C ++ - Code (keine Präprozessoranweisungen) vorgesehen ist, verwenden Sie eine normale C ++ - Variable, kein Präprozessor-Makro.Tolle Antwort von Jonathon Wakely . Ich würde Ihnen auch raten, sich die Antwort von jogojapan anzusehen, um herauszufinden , was der Unterschied zwischen
const
und istconstexpr
bevor Sie überhaupt über die Verwendung von Makros nachdenken.Makros sind dumm, aber auf gute Weise. Angeblich sind sie heutzutage eine Build-Hilfe, wenn Sie möchten, dass ganz bestimmte Teile Ihres Codes nur kompiliert werden, wenn bestimmte Build-Parameter "definiert" werden. Normalerweise bedeutet dies nur, dass Sie Ihren Makronamen verwenden, oder besser noch, nennen wir ihn a
Trigger
und fügen Dinge hinzu wie/D:Trigger
:-DTrigger
etc. zu den Build - Tools verwendet werden.Obwohl es viele verschiedene Verwendungszwecke für Makros gibt, sind dies die beiden, die ich am häufigsten sehe und die keine schlechten / veralteten Praktiken sind:
Während Sie im Fall des OP das gleiche Ziel erreichen können, ein int mit
constexpr
oder a zu definierenMACRO
, ist es unwahrscheinlich, dass sich die beiden bei Verwendung moderner Konventionen überschneiden. Hier sind einige allgemeine Makronutzungen, die noch nicht eingestellt wurden.#if defined VERBOSE || defined DEBUG || defined MSG_ALL // Verbose message-handling code here #endif
Nehmen wir als weiteres Beispiel für die Verwendung von Makros an, dass Sie eine bevorstehende Hardware veröffentlichen müssen oder eine bestimmte Generation davon, die einige knifflige Problemumgehungen enthält, die die anderen nicht benötigen. Wir definieren dieses Makro als
GEN_3_HW
.#if defined GEN_3_HW && defined _WIN64 // Windows-only special handling for 64-bit upcoming hardware #elif defined GEN_3_HW && defined __APPLE__ // Special handling for macs on the new hardware #elif !defined _WIN32 && !defined __linux__ && !defined __APPLE__ && !defined __ANDROID__ && !defined __unix__ && !defined __arm__ // Greetings, Outlander! ;) #else // Generic handling #endif
quelle