it-swarm.com.de

statische const vs #define

Ist es besser, static const vars als #define Präprozessor zu verwenden? Oder hängt es vielleicht vom Kontext ab?

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

184

Ich persönlich verabscheue den Präprozessor, also würde ich immer mit const gehen.

Der Hauptvorteil von #define besteht darin, dass in Ihrem Programm kein Speicherplatz erforderlich ist, da es wirklich nur Text durch einen Literalwert ersetzt. Es hat auch den Vorteil, dass es keinen Typ hat, sodass es für jeden ganzzahligen Wert verwendet werden kann, ohne dass Warnungen generiert werden.

Die Vorteile von "const" sind, dass sie eingeschränkt werden können, und sie können in Situationen verwendet werden, in denen ein Zeiger auf ein Objekt übergeben werden muss.

Ich weiß nicht genau, was Sie mit dem "statischen" Teil anfangen. Wenn Sie global deklarieren, würde ich sie in einen anonymen Namespace stellen, anstatt statisch zu verwenden. Zum Beispiel

namespace {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}
126
T.E.D.

Vor- und Nachteile für alles, je nach Nutzung:

  • aufzählungen
    • nur für ganzzahlige Werte möglich
    • probleme mit korrektem Gültigkeitsbereich/Bezeichnerüberschneidungen wurden gut behandelt, insbesondere in C++ 11-Aufzählungsklassen, bei denen die Aufzählungen für enum class X durch den Gültigkeitsbereich X:: eindeutig sind.
    • 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 Enumeration ein Mitglied von struct/ist. class/union), während C++ 11 standardmäßig int ist, aber vom Programmierer explizit festgelegt werden kann
    • die Adresse kann nicht verwendet werden - es gibt keine Adresse, da die Aufzählungswerte an den Verwendungspunkten effektiv inline ersetzt werden
    • strengere Nutzungsbeschränkungen (z. B. Inkrementierung - template <typename T> void f(T t) { cout << ++t; } wird nicht kompiliert, obwohl Sie eine Enumeration in eine Klasse mit implizitem Konstruktor, Casting-Operator und benutzerdefinierten Operatoren einschließen können)
    • der Typ jeder Konstanten wird aus der umschließenden Aufzählung entnommen, sodass template <typename T> void f(T) eine eindeutige Instanziierung erhält, wenn derselbe numerische Wert aus verschiedenen Aufzählungen übergeben wird, die sich alle von jeder tatsächlichen f(int) Instanziierung unterscheiden. Der Objektcode jeder Funktion könnte identisch sein (Adress-Offsets werden ignoriert), 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 sinnvollen Werte und Kombinationen geben (tatsächlich sind "legale" Kombinationen im Quellcode nicht einmal notiert, betrachten Sie enum { A = 1, B = 2 } - is A|B "legal" aus programmlogischer Sicht?)
    • 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. make und andere auf Zeitstempeln basierende Neukompilierungswerkzeuge lösen eine Neukompilierung des Clients aus, wenn sie ' wieder geändert (schlecht!)
  • consts
    • probleme mit dem Gültigkeitsbereich/Identifikatorkollisionen wurden gut behandelt
    • starker einzelner benutzerdefinierter Typ
      • sie könnten versuchen, #define ala #define S std::string("abc") "einzugeben", aber die Konstante vermeidet die wiederholte Erstellung unterschiedlicher temporärer Werte an jedem Verwendungspunkt
    • One Definition Rule Komplikationen
    • kann Adressen aufnehmen, konstante Referenzen erstellen usw.
    • am ähnlichsten einem nicht -const -Wert, der die Arbeit und die Auswirkung minimiert, wenn zwischen den beiden gewechselt wird
    • der Wert kann in die Implementierungsdatei eingefügt werden, sodass eine lokalisierte Neukompilierung und nur Client-Links zur Übernahme der Änderung möglich sind
  • definiert
    • "globaler" Geltungsbereich/anfälliger für Nutzungskonflikte, was zu schwer zu lösenden Kompilierungsproblemen und unerwarteten Laufzeitergebnissen führen kann, anstatt zu vernünftigen Fehlermeldungen; Um dies zu mildern, ist Folgendes erforderlich:
      • lange, unklare und/oder zentral koordinierte Bezeichner und der Zugriff darauf können nicht von implizit übereinstimmenden verwendeten/aktuellen/nachgeschlagenen Koenig-Namespaces, Namespace-Aliasnamen usw. profitieren.
      • während die bewährte Methode zum Trumpfen es zulässt, dass Vorlagenparameter-IDs Großbuchstaben mit einem Zeichen (möglicherweise gefolgt von einer Zahl) sind, ist die andere Verwendung von IDs ohne Kleinbuchstaben herkömmlicherweise für Präprozessordefinitionen reserviert und wird von diesen erwartet (außerhalb des Betriebssystems und der C/C++ - Bibliothek) Überschriften). Dies ist wichtig, damit die Präprozessornutzung im Unternehmensmaßstab handhabbar bleibt. Es kann erwartet werden, dass Bibliotheken von Drittanbietern die Anforderungen erfüllen. Wenn Sie dies beachten, bedeutet dies, dass die Migration vorhandener Konstanten oder Aufzählungen zu/von Definitionen eine Änderung der Groß-/Kleinschreibung mit sich bringt und daher Änderungen am Client-Quellcode erforderlich sind, anstatt eine "einfache" Neukompilierung durchzuführen. (Persönlich schreibe ich den ersten Buchstaben der Aufzählung in Großbuchstaben, nicht jedoch die Konstanten. Daher würde ich auch zwischen diesen beiden Buchstaben hin- und herwandern müssen - vielleicht ist es an der Zeit, das zu überdenken.)
    • weitere Operationen zur Kompilierungszeit möglich: String-Literal-Verkettung, Stringifizierung (Größe wird übernommen), Verkettung in Bezeichner
      • nachteil ist, dass bei #define X "x" und einigen Client-Verwendungszwecken "pre" X "post" X nicht nur durch Neukompilierung, sondern durch Erzwingen von Änderungen am Client-Code zu einer zur Laufzeit änderbaren Variablen gemacht werden soll oder muss. wohingegen dieser Übergang von einem const char* oder const std::string einfacher ist, da sie den Benutzer bereits zwingen, Verkettungsoperationen einzubeziehen (z. B. "pre" + X + "post" für string)
    • kann sizeof nicht direkt für ein definiertes numerisches Literal verwenden
    • untyped (GCC warnt nicht im Vergleich zu unsigned)
    • einige Compiler-/Linker-/Debugger-Ketten zeigen die ID möglicherweise nicht an, sodass Sie sich nur noch "magische Zahlen" ansehen müssen (Strings, was auch immer ...).
    • ich 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, abhängig von der "Implementierung", die dies nicht muss Erstellen Sie "Konstanten" wie { 1, 2 }, mit denen Arrays initialisiert werden können, oder #define MICROSECONDS *1E-6 usw. (definitiv dies wird nicht empfohlen!)
    • einige spezielle Dinge wie __FILE__ und __LINE__ können in die Makrosubstitution einbezogen werden
    • sie können in #if Anweisungen auf Existenz und Wert testen, um Code bedingt einzuschließen (leistungsfähiger als eine Nachbearbeitung, wenn der Code nicht kompilierbar sein muss, wenn er nicht vom Präprozessor ausgewählt wurde). Verwenden Sie #undef -ine, neu definieren usw.
    • ersetzter Text muss freigelegt werden:
      • in der Übersetzungseinheit wird sie von verwendet. Dies bedeutet, dass Makros in Bibliotheken für die Client-Verwendung im Header enthalten sein müssen. make und andere auf Zeitstempeln basierende Neukompilierungs-Tools lösen eine Neukompilierung des Clients aus, wenn sie geändert werden (fehlerhaft!).
      • oder in der Befehlszeile, wo noch mehr Sorgfalt erforderlich ist, um sicherzustellen, dass der Clientcode erneut kompiliert wird (z. B. sollte das Makefile oder Skript, das die Definition bereitstellt, als Abhängigkeit aufgeführt werden).

Im Allgemeinen 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).

224
Tony Delroy

Wenn dies eine C++ - Frage ist und als Alternative #define erwähnt wird, handelt es sich um "globale" Konstanten (d. H. Dateibereich), nicht um Klassenmitglieder. Wenn es um solche Konstanten in C++ geht, ist static const überflüssig. In C++ haben const standardmäßig eine interne Verknüpfung und es ist sinnlos, sie als static zu deklarieren. Es geht also wirklich um const vs. #define

Und schließlich ist in C++ const vorzuziehen. Zumindest deshalb, weil solche Konstanten typisiert und definiert sind. Es gibt einfach keine Gründe, #define gegenüber const vorzuziehen, abgesehen von wenigen Ausnahmen.

String-Konstanten (BTW) sind ein Beispiel für eine solche Ausnahme. Mit #defined-String-Konstanten kann die Kompilierungsfunktion 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;

P.S. Nur für den Fall, dass jemand static const als Alternative zu #define erwähnt, bedeutet dies normalerweise, dass er über C und nicht über C++ spricht. Ich frage mich, ob diese Frage richtig markiert wurde ...

41
AnT

Die Verwendung einer statischen const-Anweisung ist vergleichbar mit den anderen const-Variablen in Ihrem Code. Dies bedeutet, dass Sie nachverfolgen können, woher die Informationen stammen, im Gegensatz zu einem #define-Element, das im Pre-Compilierungsprozess einfach im Code ersetzt wird.

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

5
Percutio
  • Eine statische const ist typisiert (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 für wirklich schmutzige Trics).

4
RED SOFT ADAIR

Das Definieren von Konstanten mithilfe der Präprozessor-Direktive #define wird nicht empfohlen, nicht nur in C++, sondern auch in C anzuwenden. Diese Konstanten haben nicht den Typ. Sogar in C wurde vorgeschlagen, const für Konstanten zu verwenden.

1
Aleksey Bykov

#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 #define einfach den Text ersetzt. Da dies die Reihenfolge der Operationen ernsthaft durcheinander bringen kann, würde ich empfehlen, stattdessen eine konstante Variable zu verwenden.

1
Juniorized

Bitte sehen Sie hier: static const vs define

normalerweise ist eine const-Deklaration (beachten Sie, dass es nicht statisch sein muss) der richtige Weg

1
ennuikiller

Bevorzugen Sie immer die Sprachfunktionen gegenüber einigen zusätzlichen Tools wie dem Präprozessor.

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

Makros sind eine Hauptursache für Fehler. Makros befolgen nicht den üblichen Bereich und geben Sie Regeln ein. Makros befolgen nicht die üblichen Regeln für Argument Vorbeigehen. Makros sorgen dafür, dass der menschliche Leser etwas anderes sieht von dem, was der Compiler sieht. Makros erschweren das Erstellen von Werkzeugen.

Von C++ - Kernrichtlinien

0
Hitokage

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

0
snr