statische const vs #define


212

Ist es besser, static constvars als #definePräprozessor zu verwenden? Oder hängt es vielleicht vom Kontext ab?

Was sind Vor- / Nachteile für jede Methode?


14
Scott Meyers behandelt dieses Thema sehr schön und gründlich. Sein Artikel Nr. 2 in "Effective C ++ Third Edition". Zwei Sonderfälle (1) Eine statische Konstante wird innerhalb eines Klassenbereichs für klassenspezifische Konstanten bevorzugt. (2) Namespace oder anonyme Bereichskonstante wird gegenüber #define bevorzugt.
Eric

2
Ich bevorzuge Enums. Weil es eine Mischung aus beidem ist. Nimmt keinen Platz ein, es sei denn, Sie erstellen eine Variable davon. Wenn Sie nur als Konstante verwenden möchten, ist enum die beste Option. Es hat Typensicherheit in C / C ++ 11 Standard und auch eine perfekte Konstante. #define ist vom Typ unsicher, const benötigt Speicherplatz, wenn der Compiler ihn nicht optimieren kann.
Siddhusingh

1
Meine Entscheidung, ob #defineoder static const(für Zeichenfolgen) verwendet werden soll, hängt vom Initialisierungsaspekt ab (der in den folgenden Antworten nicht erwähnt wurde): Wenn die Konstante nur innerhalb einer bestimmten Kompilierungseinheit verwendet wird, gehe ich mit static const, ansonsten verwende ich #define- vermeiden Sie das Fiasko der Initialisierung der statischen Reihenfolge isocpp.org/wiki/faq/ctors#static-init-order
Martin Dvorak

Wenn const, constexproder enumoder jegliche Variation Werke in Ihrem Fall bevorzugen sie dann#define
Phil1970

@MartinDvorak " Fiasko bei der Initialisierung statischer Ordnungen vermeiden " Wie ist das ein Problem für Konstanten?
neugieriger Kerl

Antworten:


139

Persönlich verabscheue ich den Präprozessor, also würde ich immer mitgehen const.

Der Hauptvorteil von a #definebesteht darin, dass zum Speichern in Ihrem Programm kein Speicher erforderlich ist, da nur Text durch einen Literalwert ersetzt wird. Es hat auch den Vorteil, dass es keinen Typ hat, so dass es für jeden ganzzahligen Wert verwendet werden kann, ohne Warnungen zu generieren.

Die Vorteile von " const" s bestehen darin, dass sie einen Gültigkeitsbereich haben und in Situationen verwendet werden können, in denen ein Zeiger auf ein Objekt übergeben werden muss.

Ich weiß allerdings nicht genau, was Sie mit dem " static" Teil anfangen. Wenn Sie global deklarieren, würde ich es in einen anonymen Namespace stellen, anstatt es zu verwenden static. Beispielsweise

namespace {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}

8
String- Konstanten sind speziell diejenigen, die von #defined profitieren könnten , zumindest wenn sie als "Bausteine" für größere String-Konstanten verwendet werden können. Siehe meine Antwort für ein Beispiel.
Am

62
Der #defineVorteil, keinen Speicher zu verwenden, ist ungenau. Die "60" im Beispiel muss irgendwo gespeichert werden, unabhängig davon, ob es sich um static constoder handelt #define. Tatsächlich habe ich Compiler gesehen, bei denen die Verwendung von #define einen massiven (schreibgeschützten) Speicherverbrauch verursachte und die statische Konstante keinen nicht benötigten Speicher verwendete.
Gilad Naor

3
Ein #define ist so, als hätten Sie es eingegeben, es kommt also definitiv nicht aus dem Speicher.
der Reverend

27
@theReverend Sind Literalwerte irgendwie vom Verbrauch von Maschinenressourcen befreit? Nein, sie verwenden sie möglicherweise auf unterschiedliche Weise. Möglicherweise wird sie nicht auf dem Stapel oder Heap angezeigt, aber irgendwann wird das Programm zusammen mit allen darin kompilierten Werten in den Speicher geladen.
Sqeaky

13
@ gilad-naor, Sie haben im Allgemeinen Recht, aber kleine ganze Zahlen wie 60 können tatsächlich manchmal eine Art Teilausnahme sein. Einige Befehlssätze können Ganzzahlen oder eine Teilmenge von Ganzzahlen direkt im Befehlsstrom codieren. Beispielsweise werden MIPs sofort hinzugefügt ( cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/addi.html ). In einem solchen Fall kann man sagen, dass eine # definierte Ganzzahl wirklich keinen Platz belegt, da sie in der kompilierten Binärdatei einige freie Bits in Anweisungen belegt, die ohnehin existieren mussten.
Ahcox

241

Vor- und Nachteile zwischen #defines, consts und (was Sie vergessen haben) enums, je nach Verwendung:

  1. enums:

    • nur für ganzzahlige Werte möglich
    • Probleme mit ordnungsgemäßem Gültigkeitsbereich / Identifizierungskonflikten werden gut behandelt, insbesondere in C ++ 11-Aufzählungsklassen, in denen die Aufzählungen für enum class Xdurch den Bereich eindeutig sindX::
    • stark typisiert, aber auf eine ausreichend große int-Größe mit oder ohne Vorzeichen, über die Sie in C ++ 03 keine Kontrolle haben (obwohl Sie ein Bitfeld angeben können, in das sie gepackt werden sollen, wenn die Aufzählung ein Mitglied von struct / ist class / union), während C ++ 11 standardmäßig verwendet wird, intaber vom Programmierer explizit festgelegt werden kann
    • Ich kann die Adresse nicht annehmen - es gibt keine, da die Aufzählungswerte an den Verwendungspunkten effektiv inline ersetzt werden
    • Stärkere Nutzungsbeschränkungen (z. B. Inkrementieren - template <typename T> void f(T t) { cout << ++t; }wird nicht kompiliert, obwohl Sie eine Aufzählung mit implizitem Konstruktor, Casting-Operator und benutzerdefinierten Operatoren in eine Klasse einschließen können).
    • Der Typ jeder Konstante stammt aus der einschließenden Aufzählung. template <typename T> void f(T)Erhalten Sie also eine eindeutige Instanziierung, wenn Sie denselben numerischen Wert aus verschiedenen Aufzählungen übergeben, die sich alle von jeder tatsächlichen f(int)Instanziierung unterscheiden. Der Objektcode jeder Funktion könnte identisch sein (ohne Berücksichtigung von Adressversätzen), aber ich würde nicht erwarten, dass ein Compiler / Linker die unnötigen Kopien eliminiert, obwohl Sie Ihren Compiler / Linker überprüfen könnten, wenn Sie sich darum kümmern.
    • Selbst mit typeof / decltype kann numeric_limits keinen nützlichen Einblick in die Menge der aussagekräftigen Werte und Kombinationen bieten (tatsächlich werden "legale" Kombinationen im Quellcode nicht einmal notiert, bedenken Sie enum { A = 1, B = 2 }- ist A|B"legal" aus einer Programmlogik Perspektive?)
    • Der Typname der Aufzählung kann an verschiedenen Stellen in RTTI, Compilermeldungen usw. erscheinen - möglicherweise nützlich, möglicherweise verschleiert
    • Sie können keine Aufzählung verwenden, ohne dass die Übersetzungseinheit den Wert tatsächlich sieht. Dies bedeutet, dass Aufzählungen in Bibliotheks-APIs die im Header angegebenen Werte benötigen. makeAndere auf Zeitstempeln basierende Neukompilierungswerkzeuge lösen eine Client-Neukompilierung aus, wenn sie geändert werden (fehlerhaft! )

  1. consts:

    • Probleme mit dem richtigen Umfang / Identifizierungskonflikt werden gut behandelt
    • starker, einzelner, benutzerdefinierter Typ
      • Sie könnten versuchen, ein #defineala zu "tippen" #define S std::string("abc"), aber die Konstante vermeidet die wiederholte Konstruktion unterschiedlicher Provisorien an jedem Verwendungspunkt
    • Komplikationen mit einer Definitionsregel
    • kann Adressen annehmen, konstante Verweise darauf erstellen usw.
    • Am ähnlichsten einem Nichtwert const, der Arbeit und Auswirkungen beim Wechsel zwischen beiden minimiert
    • Der Wert kann in die Implementierungsdatei eingefügt werden, sodass eine lokalisierte Neukompilierung und nur Client-Links die Änderung übernehmen können

  1. #defines:

    • "globaler" Bereich / anfälliger für widersprüchliche Verwendungen, die zu schwer zu lösenden Kompilierungsproblemen und unerwarteten Laufzeitergebnissen führen können, anstatt zu vernünftigen Fehlermeldungen; Dies zu mildern erfordert:
      • lange, obskure und / oder zentral koordinierte Bezeichner, und der Zugriff auf diese kann nicht davon profitieren, dass gebrauchte / aktuelle / von Koenig nachgeschlagene Namespaces, Namespace-Aliase usw. implizit abgeglichen werden.
      • Während die bewährte Methode für das Trumping ermöglicht, dass Bezeichner von Vorlagenparametern einstellige Großbuchstaben (möglicherweise gefolgt von einer Zahl) sind, ist die andere Verwendung von Bezeichnern ohne Kleinbuchstaben herkömmlicherweise für Präprozessordefinitionen reserviert und wird von diesen erwartet (außerhalb der Betriebssystem- und C / C ++ - Bibliothek) Überschriften). Dies ist wichtig, damit die Verwendung von Präprozessoren im Unternehmensmaßstab überschaubar bleibt. Es ist zu erwarten, dass Bibliotheken von Drittanbietern die Anforderungen erfüllen. Wenn Sie dies beobachten, bedeutet die Migration vorhandener Konstanten oder Aufzählungen zu / von Definitionen eine Änderung der Großschreibung und erfordert daher Änderungen am Client-Quellcode anstelle einer "einfachen" Neukompilierung. (Ich persönlich schreibe den ersten Buchstaben der Aufzählungen groß, aber keine Konstanten, daher würde ich auch von der Migration zwischen diesen beiden betroffen sein - vielleicht ist es an der Zeit, das zu überdenken.)
    • Weitere Operationen zur Kompilierungszeit möglich: Verkettung von Zeichenfolgenliteralen, Zeichenfolge (unter Berücksichtigung ihrer Größe), Verkettung in Bezeichner
      • Nachteil ist , dass gegeben #define X "x"und einige Client - Nutzung ala "pre" X "post", wenn Sie wollen oder müssen , um X eine Laufzeit veränderliche Variable anstatt eine Konstante Sie Änderungen erzwingen Client - Code ( und nicht nur neu kompiliert), während dieser Übergang leichter von einem ist const char*oder const std::stringsie gegeben den Benutzer bereits zwingen, Verkettungsoperationen einzubeziehen (z. B. "pre" + X + "post"für string)
    • kann nicht sizeofdirekt für ein definiertes numerisches Literal verwendet werden
    • untypisiert (GCC warnt nicht im Vergleich zu unsigned)
    • Einige Compiler- / Linker- / Debugger-Ketten enthalten möglicherweise keine Kennung, sodass Sie sich nur noch auf "magische Zahlen" (Zeichenfolgen, was auch immer ...) konzentrieren müssen.
    • kann die Adresse nicht nehmen
    • Der ersetzte Wert muss in dem Kontext, in dem #define erstellt wird, nicht legal (oder diskret) sein, da er an jedem Verwendungspunkt ausgewertet wird, sodass Sie auf noch nicht deklarierte Objekte verweisen können. Dies hängt von der "Implementierung" ab, die nicht benötigt wird vorab enthalten sein, "Konstanten" erstellen, wie { 1, 2 }sie zum Initialisieren von Arrays #define MICROSECONDS *1E-6usw. verwendet werden können (dies wird definitiv nicht empfohlen!)
    • Einige spezielle Dinge wie __FILE__und __LINE__können in die Makrosubstitution integriert werden
    • Sie können in #ifAnweisungen auf Existenz und Wert testen, um Code unter bestimmten Bedingungen einzuschließen (leistungsfähiger als eine Nachverarbeitung "if", da der Code nicht kompilierbar sein muss, wenn er nicht vom Präprozessor ausgewählt wurde), #undef-ine verwenden, neu definieren usw.
    • Ersetzter Text muss verfügbar gemacht werden:
      • in der Übersetzungseinheit, von der es verwendet wird, was bedeutet, dass sich Makros in Bibliotheken für die Client-Verwendung im Header befinden müssen, sodass makeandere auf Zeitstempeln basierende Neukompilierungswerkzeuge die Neukompilierung des Clients auslösen, wenn sie geändert werden (schlecht!)
      • oder in der Befehlszeile, wo noch mehr Sorgfalt erforderlich ist, um sicherzustellen, dass der Clientcode neu kompiliert wird (z. B. sollte das Makefile oder Skript, das die Definition liefert, als Abhängigkeit aufgeführt werden).

Meine persönliche Meinung:

In der Regel verwende ich consts und betrachte sie als die professionellste Option für den allgemeinen Gebrauch (obwohl die anderen eine Einfachheit haben, die diesen alten faulen Programmierer anspricht).


1
Tolle Antwort. Ein kleiner Fehler: Ich verwende manchmal lokale Aufzählungen, die sich überhaupt nicht in Headern befinden, nur um den Code klarer zu machen, wie bei kleinen Zustandsautomaten und dergleichen. Sie müssen also nicht immer in Kopfzeilen stehen.
kert

Die Vor- und Nachteile sind verwechselt, ich würde sehr gerne eine Vergleichstabelle sehen.
Unbekannt123

@ Unknown123: zögern Sie nicht, einen zu posten - es macht mir nichts aus, wenn Sie Punkte abreißen, die Sie von hier aus für würdig halten. Prost
Tony Delroy

48

Wenn es sich um eine C ++ - Frage handelt, die #defineals Alternative erwähnt wird, handelt es sich um "globale" Konstanten (dh Dateibereichskonstanten), nicht um Klassenmitglieder. Wenn es um solche Konstanten in C ++ geht, static constist dies redundant. In C ++ constsind standardmäßig interne Verknüpfungen vorhanden, und es macht keinen Sinn, diese zu deklarieren static. So ist es wirklich über constvs. #define.

Und schließlich ist in C ++ constvorzuziehen. Zumindest, weil solche Konstanten typisiert und mit Gültigkeitsbereich versehen sind. Es gibt einfach keinen Grund zu bevorzugen #defineüber const, abgesehen von wenigen Ausnahmen.

String-Konstanten, BTW, sind ein Beispiel für eine solche Ausnahme. Mit #defined String-Konstanten kann die Verkettungsfunktion zur Kompilierungszeit von C / C ++ - Compilern wie in verwendet werden

#define OUT_NAME "output"
#define LOG_EXT ".log"
#define TEXT_EXT ".txt"

const char *const log_file_name = OUT_NAME LOG_EXT;
const char *const text_file_name = OUT_NAME TEXT_EXT;

PS Noch einmal, nur für den Fall, dass jemand static constals Alternative zu erwähnt #define, bedeutet dies normalerweise, dass er über C spricht, nicht über C ++. Ich frage mich, ob diese Frage richtig markiert ist ...


1
" einfach keine Gründe, #define vorzuziehen " gegenüber was? In einer Header-Datei definierte statische Variablen?
Neugieriger

9

#define kann zu unerwarteten Ergebnissen führen:

#include <iostream>

#define x 500
#define y x + 5

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Gibt ein falsches Ergebnis aus:

y is 505
z is 510

Wenn Sie dies jedoch durch Konstanten ersetzen:

#include <iostream>

const int x = 500;
const int y = x + 5;

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Es gibt das richtige Ergebnis aus:

y is 505
z is 1010

Dies liegt daran, dass #defineder Text einfach ersetzt wird. Da dies die Reihenfolge der Operationen ernsthaft beeinträchtigen kann, würde ich empfehlen, stattdessen eine konstante Variable zu verwenden.


1
Ich hatte ein anderes unerwartetes Ergebnis: yhatte den Wert 5500, eine Little-Endian-Verkettung von xund 5.
Codes mit Hammer

5

Die Verwendung einer statischen Konstante entspricht der Verwendung anderer Konstantenvariablen in Ihrem Code. Dies bedeutet, dass Sie verfolgen können, woher die Informationen stammen, im Gegensatz zu einem #define, das im Vorkompilierungsprozess einfach im Code ersetzt wird.

Vielleicht möchten Sie sich die C ++ FAQ Lite für diese Frage ansehen: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7


4
  • Eine statische Konstante wird eingegeben (sie hat einen Typ) und kann vom Compiler auf Gültigkeit, Neudefinition usw. überprüft werden.
  • Ein #define kann undefiniert neu definiert werden.

Normalerweise sollten Sie statische Konstanten bevorzugen. Es hat keinen Nachteil. Der Prozessor sollte hauptsächlich für die bedingte Kompilierung verwendet werden (und manchmal auch für wirklich schmutzige Trics).


3

Das Definieren von Konstanten mithilfe der Präprozessor-Direktive #definewird nicht empfohlen, nicht nur in C++, sondern auch in anzuwenden C. Diese Konstanten haben nicht den Typ. Auch in Cwurde vorgeschlagen, constfür Konstanten zu verwenden.



2

Ziehen Sie es immer vor, die Sprachfunktionen gegenüber einigen zusätzlichen Tools wie dem Präprozessor zu verwenden.

ES.31: Verwenden Sie keine Makros für Konstanten oder "Funktionen"

Makros sind eine Hauptquelle für Fehler. Makros halten sich nicht an die üblichen Regeln für Umfang und Typ. Makros befolgen nicht die üblichen Regeln für die Übergabe von Argumenten. Makros stellen sicher, dass der menschliche Leser etwas anderes sieht als der Compiler. Makros erschweren die Werkzeugerstellung.

Aus den C ++ - Kernrichtlinien


0

Wenn Sie eine Konstante definieren, die von allen Instanzen der Klasse gemeinsam genutzt werden soll, verwenden Sie die statische Konstante. Wenn die Konstante für jede Instanz spezifisch ist, verwenden Sie einfach const (beachten Sie jedoch, dass alle Konstruktoren der Klasse diese const-Mitgliedsvariable in der Initialisierungsliste initialisieren müssen).

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.