Definieren Sie konstante Variablen im C ++ - Header

73

Ein Programm, an dem ich arbeite, hat viele Konstanten, die für alle Klassen gelten. Ich möchte eine Header-Datei "Constants.h" erstellen und alle relevanten Konstanten deklarieren können. Dann kann ich in meinen anderen Klassen einfach aufnehmen #include "Constants.h.

Ich habe es mit #ifndef... #define ...Syntax gut funktionieren lassen . Ich würde jedoch lieber die const int...Form von Konstanten verwenden. Ich bin mir allerdings nicht ganz sicher, wie.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
5
Können Sie erklären, was Sie nicht verstehen const int? Beispielcode posten, der nicht kompiliert werden konnte, wenn das Ihre Frage ist?
Djechlin

Antworten:

96

Sie können einfach eine Reihe von const intsin einer Header-Datei definieren:

// Constants.h
#if !defined(MYLIB_CONSTANTS_H)
#define MYLIB_CONSTANTS_H 1

const int a = 100;
const int b = 0x7f;

#endif

Dies funktioniert, weil in C ++ ein Name im Namespace-Bereich (einschließlich des globalen Namespace), der explizit als const deklariert und nicht explizit als extern deklariert ist, eine interne Verknüpfung aufweist, sodass diese Variablen beim Verknüpfen von Übersetzungseinheiten keine doppelten Symbole verursachen würden. Alternativ können Sie die Konstanten explizit als statisch deklarieren.

static const int a = 100;
static const int b = 0x7f;

Dies ist kompatibler mit C und besser lesbar für Personen, die möglicherweise nicht mit C ++ - Verknüpfungsregeln vertraut sind.

Wenn alle Konstanten Ints sind, können Sie die Bezeichner auch als Aufzählungen deklarieren.

enum mylib_constants {
    a = 100;
    b = 0x7f;
};

Alle diese Methoden verwenden nur einen Header und ermöglichen die Verwendung der deklarierten Namen als Kompilierungszeitkonstanten. Die Verwendung extern const inteiner separaten Implementierungsdatei verhindert, dass die Namen als Konstanten für die Kompilierungszeit verwendet werden.


Beachten Sie, dass die Regel , dass bestimmte Konstanten implizit interne Bindung macht sich auf Zeiger anwenden, genau wie Konstanten anderer Typen. Das Schwierige ist jedoch, dass das Markieren eines Zeigers als constetwas andere Syntax erfordert, die die meisten Leute verwenden, um Variablen anderer Typen const zu machen. Sie müssen tun:

int * const ptr;

einen konstanten Zeiger zu machen, damit die Regel auf ihn angewendet wird.

Beachten Sie auch, dass dies ein Grund ist, warum ich es vorziehe, konsequent constnach dem Typ zu setzen: int conststatt const int. Ich setze auch das *nächste neben die Variable: dh int *ptr;anstelle von int* ptr;(vergleiche auch diese Diskussion).

Ich mache solche Dinge gerne, weil sie den allgemeinen Fall widerspiegeln, wie C ++ wirklich funktioniert. Die Alternativen ( const int, int* p) sind nur spezielle Gehäuse, um einige einfache Dinge besser lesbar zu machen. Das Problem ist, dass beim Verlassen dieser einfachen Fälle die speziellen Alternativen aktiv irreführend werden.

Obwohl die früheren Beispiele die allgemeine Verwendung von zeigen const, würde ich den Leuten tatsächlich empfehlen, sie so zu schreiben:

int const a = 100;
int const b = 0x7f;

und

static int const a = 100;
static int const b = 0x7f;
bames53
quelle
1
Ihre Regel mit impliziter statischer Verknüpfung funktioniert nicht mit Zeigern
yanpas
8
@yanpas Es funktioniert jedoch, Sie müssen sicherstellen, dass Sie die constan der richtigen Stelle platzieren. Um einen Zeiger zu consterstellen, muss das constnach dem gesetzt werden *. ZB int * const x. Wenn Sie nur verwenden, bezieht sich const int *x;das constauf das, worauf der Zeiger zeigt, und nicht auf den Zeiger selbst.
Bames53
31

Ich mag den Namespace für diesen Zweck besser.

Option 1 :

#ifndef MYLIB_CONSTANTS_H
#define MYLIB_CONSTANTS_H

//  File Name : LibConstants.hpp    Purpose : Global Constants for Lib Utils
namespace LibConstants
{
  const int CurlTimeOut = 0xFF;     // Just some example
  ...
}
#endif

// source.cpp
#include <LibConstants.hpp>
int value = LibConstants::CurlTimeOut;

Option 2 :

#ifndef MYLIB_CONSTANTS_H
#define MYLIB_CONSTANTS_H
//  File Name : LibConstants.hpp    Purpose : Global Constants for Lib Utils
namespace CurlConstants
{
  const int CurlTimeOut = 0xFF;     // Just some example
  ...
}

namespace MySQLConstants
{
  const int DBPoolSize = 0xFF;      // Just some example
  ...
}
#endif



// source.cpp
#include <LibConstants.hpp>
int value = CurlConstants::CurlTimeOut;
int val2  = MySQLConstants::DBPoolSize;

Und ich würde niemals eine Klasse verwenden, um diese Art von HardCoded Const-Variablen zu halten.

Manikandaraj Srinivasan
quelle
Ich mag diese Idee. Aber heißt das, dass ich und alle anderen Konstanten initialisieren valuemuss val2? Gibt es eine Möglichkeit, den Namespace in die Funktion aufzunehmen und dieselben konstanten Namen wie im Namespace zu verwenden?
TalekeDskobeDa
19

Sie sollten zB const intin einer Header-Datei im Allgemeinen nicht verwenden , wenn sie in mehreren Quelldateien enthalten ist. Dies liegt daran, dass die Variablen dann einmal pro Quelldatei definiert werden (technisch gesehen Übersetzungseinheiten), da globale constVariablen implizit statisch sind und mehr Speicher als erforderlich beanspruchen.

Sie sollten stattdessen eine spezielle Quelldatei haben Constants.cpp, die die Variablen tatsächlich definiert, und dann die Variablen wie externin der Header-Datei deklarieren lassen .

So etwas wie diese Header-Datei:

// Protect against multiple inclusions in the same source file
#ifndef CONSTANTS_H
#define CONSTANTS_H

extern const int CONSTANT_1;

#endif

Und das in einer Quelldatei:

const int CONSTANT_1 = 123;
Ein Programmierer
quelle
1
@arasmussen Nicht, wenn Sie nur eine einzige Header-Datei verwenden möchten. Siehe meine überarbeitete Antwort.
Einige Programmierer Typ
3
Es ist besser, es im Header zu initialisieren, damit es als Konstante für die Kompilierungszeit verwendet werden kann.
Mike Seymour
13
Sie können es auch const intin einer Header-Datei verwenden, da constVariablen standardmäßig eine interne Verknüpfung haben. Ob Sie das anstelle einer einzelnen Instanz möchten, ist eine andere Frage. Normalerweise spielt es keine Rolle, solange der Wert in der Kopfzeile verfügbar ist.
Mike Seymour
4
C ++ 17 Inline-Variablen sind eine noch bessere Möglichkeit, dies für diejenigen zu erreichen, die diese Version verwenden können: stackoverflow.com/a/53541011/895245
Ciro Santilli 22 冠状 病 六四 事件 22
1
@ Jamāl Es sei denn, es gibt eine vorherige Deklaration, die eine externe Verknüpfung erzwingt, z. B. das Einfügen der Header-Datei, die die externDeklaration enthält.
Ein Programmierer
14

C ++ 17 inlineVariablen

Mit dieser fantastischen C ++ 17-Funktion können wir:

  • Verwenden Sie bequemerweise nur eine einzige Speicheradresse für jede Konstante
  • Speichern Sie es als constexpr: Wie deklariere ich constexpr extern?
  • Machen Sie es in einer einzigen Zeile aus einem Header

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Kompilieren und ausführen:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub stromaufwärts .

Siehe auch: Wie funktionieren Inline-Variablen?

C ++ - Standard für Inline-Variablen

Der C ++ - Standard garantiert, dass die Adressen gleich sind. C ++ 17 N4659 Standardentwurf 10.1.6 "Der Inline-Spezifizierer":

6 Eine Inline-Funktion oder Variable mit externer Verknüpfung muss in allen Übersetzungseinheiten dieselbe Adresse haben.

cppreference https://en.cppreference.com/w/cpp/language/inline erklärt, dass, wenn staticnicht angegeben, eine externe Verknüpfung besteht.

Inline-Variablenimplementierung

Wir können beobachten, wie es implementiert wird mit:

nm main.o notmain.o

was beinhaltet:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

und man nmsagt über u:

"u" Das Symbol ist ein eindeutiges globales Symbol. Dies ist eine GNU-Erweiterung des Standardsatzes von ELF-Symbolbindungen. Für ein solches Symbol stellt der dynamische Linker sicher, dass im gesamten Prozess nur ein Symbol mit diesem Namen und Typ verwendet wird.

Wir sehen also, dass es dafür eine dedizierte ELF-Erweiterung gibt.

C ++ 17 Standardentwurf zu "global" constimpliziertstatic

Dies ist das Zitat für das, was unter https://stackoverflow.com/a/12043198/895245 erwähnt wurde

C ++ 17 n4659 Standardentwurf 6.5 "Programm und Verknüpfung":

3 Ein Name mit Namespace-Bereich (6.3.6) hat eine interne Verknüpfung, wenn es sich um den Namen von handelt

  • (3.1) - eine Variable, Funktion oder Funktionsvorlage, die explizit als statisch deklariert ist; oder,
  • (3.2) - eine nicht inline Variable vom nichtflüchtigen const-qualifizierten Typ, die weder explizit als extern deklariert noch zuvor als extern verknüpft deklariert wurde; oder
  • (3.3) - ein Datenmitglied einer anonymen Gewerkschaft.

Der Bereich "Namespace" wird umgangssprachlich häufig als "global" bezeichnet.

Anhang C (informativ) Kompatibilität, C.1.2 Abschnitt 6: "Grundbegriffe" begründet, warum dies von C geändert wurde:

6.5 [auch 10.1.7]

Änderung: Ein Name des Dateibereichs, der explizit als const deklariert und nicht explizit als extern deklariert wird, hat eine interne Verknüpfung, während er in C eine externe Verknüpfung hätte.

Begründung: Da const-Objekte während der Übersetzung in C ++ als Werte verwendet werden können, fordert diese Funktion Programmierer auf, für jedes const-Objekt einen expliziten Initialisierer bereitzustellen. Mit dieser Funktion kann der Benutzer const-Objekte in Quelldateien einfügen, die in mehr als einer Übersetzungseinheit enthalten sind.

Auswirkung auf das ursprüngliche Merkmal: Wechseln Sie zur Semantik eines genau definierten Merkmals.

Schwierigkeiten beim Konvertieren: Semantische Transformation.

Wie weit verbreitet: Selten.

Siehe auch: Warum impliziert const eine interne Verknüpfung in C ++, wenn dies in C nicht der Fall ist?

Getestet in GCC 7.4.0, Ubuntu 18.04.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
1

Anstatt eine Reihe globaler Variablen zu erstellen, können Sie auch eine Klasse mit einer Reihe öffentlicher statischer Konstanten erstellen. Es ist immer noch global, aber auf diese Weise ist es in eine Klasse eingeschlossen, sodass Sie wissen, woher die Konstante kommt und dass es eine Konstante sein soll.

Konstanten.h

#ifndef CONSTANTS_H
#define CONSTANTS_H

class GlobalConstants {
  public:
    static const int myConstant;
    static const int myOtherConstant;
};

#endif

Constants.cpp

#include "Constants.h"

const int GlobalConstants::myConstant = 1;
const int GlobalConstants::myOtherConstant = 3;

Dann können Sie dies wie folgt verwenden:

#include "Constants.h"

void foo() {
  int foo = GlobalConstants::myConstant;
}
Andrew Rasmussen
quelle
48
Warum sollten Sie eine Klasse verwenden, um einen Namespace zu emulieren, wenn Sie nur einen Namespace verwenden könnten?
Mike Seymour
0

Es scheint, dass die Antwort von bames53 auf das Definieren von ganzzahligen und nicht ganzzahligen konstanten Werten in Namespace- und Klassendeklarationen erweitert werden kann , selbst wenn sie in mehreren Quelldateien enthalten sind. Es ist nicht erforderlich, die Deklarationen in einer Header-Datei, sondern die Definitionen in einer Quelldatei abzulegen. Das folgende Beispiel funktioniert für Microsoft Visual Studio 2015, für z / OS V2.2 XL C / C ++ unter OS / 390 und für g ++ (GCC) 8.1.1 20180502 unter GNU / Linux 4.16.14 (Fedora 28). Beachten Sie, dass die Konstanten nur in einer einzelnen Header-Datei deklariert / definiert werden, die in mehreren Quelldateien enthalten ist.

In foo.cc:

#include <cstdio>               // for puts

#include "messages.hh"
#include "bar.hh"
#include "zoo.hh"

int main(int argc, const char* argv[])
{
  puts("Hello!");
  bar();
  zoo();
  puts(Message::third);
  return 0;
}

In messages.hh:

#ifndef MESSAGES_HH
#define MESSAGES_HH

namespace Message {
  char const * const first = "Yes, this is the first message!";
  char const * const second = "This is the second message.";
  char const * const third = "Message #3.";
};

#endif

In bar.cc:

#include "messages.hh"
#include <cstdio>

void bar(void)
{
  puts("Wow!");
  printf("bar: %s\n", Message::first);
}

In zoo.cc:

#include <cstdio>
#include "messages.hh"

void zoo(void)
{
  printf("zoo: %s\n", Message::second);
}

In bar.hh:

#ifndef BAR_HH
#define BAR_HH

#include "messages.hh"

void bar(void);

#endif

In zoo.hh:

#ifndef ZOO_HH
#define ZOO_HH

#include "messages.hh"

void zoo(void);

#endif

Dies ergibt die folgende Ausgabe:

Hello!
Wow!
bar: Yes, this is the first message!
zoo: This is the second message.
Message #3.

Der Datentyp char const * constbedeutet einen konstanten Zeiger auf ein Array konstanter Zeichen. Die erste constwird benötigt, weil (laut g ++) "ISO C ++ die Konvertierung einer String-Konstante in 'char *' verbietet". Die zweite constist erforderlich, um Verbindungsfehler aufgrund mehrerer Definitionen der (dann nicht ausreichend konstanten) Konstanten zu vermeiden. Ihr Compiler beschwert sich möglicherweise nicht, wenn Sie eines oder beide der consts weglassen , aber der Quellcode ist weniger portabel.

Louis Strous
quelle