Definieren statischer const integer-Elemente in der Klassendefinition

108

Nach meinem Verständnis können in C ++ statische const-Mitglieder innerhalb einer Klasse definiert werden, sofern es sich um einen Integer-Typ handelt.

Warum gibt mir der folgende Code dann einen Linkerfehler?

#include <algorithm>
#include <iostream>

class test
{
public:
    static const int N = 10;
};

int main()
{
    std::cout << test::N << "\n";
    std::min(9, test::N);
}

Der Fehler, den ich bekomme, ist:

test.cpp:(.text+0x130): undefined reference to `test::N'
collect2: ld returned 1 exit status

Interessanterweise kompiliert und verknüpft der Code, wenn ich den Aufruf von std :: min auskommentiere, einwandfrei (obwohl auf test :: N auch in der vorherigen Zeile verwiesen wird).

Irgendeine Idee, was los ist?

Mein Compiler ist gcc 4.4 unter Linux.

HighCommander4
quelle
3
Funktioniert gut in Visual Studio 2010.
Welpe
4
Dieser genaue Fehler wird unter gcc.gnu.org/wiki/…
Jonathan Wakely am
Im speziellen Fall von charkönnen Sie es stattdessen als definieren constexpr static const char &N = "n"[0];. Beachten Sie die &. Ich denke, das funktioniert, weil Literal-Strings automatisch definiert werden. Ich mache mir allerdings ein bisschen Sorgen darüber - es könnte sich in einer Header-Datei zwischen verschiedenen Übersetzungseinheiten seltsam verhalten, da sich die Zeichenfolge wahrscheinlich an mehreren verschiedenen Adressen befindet.
Aaron McDaid
1
Diese Frage zeigt, wie schlecht die C ++ - Antwort auf "#defines nicht für Konstanten verwenden" immer noch ist.
Johannes Overmann
1
@JohannesOvermann In diesem Zusammenhang möchte ich die Verwendung von Inline für globale Variablen seit C ++ 17 erwähnen inline const int N = 10, das meines Wissens immer noch einen Speicher hat, der irgendwo durch Linker definiert ist. Schlüsselwort inline könnte auch in diesem Fall verwendet werden , um statische Variable zu schaffen Definition innerhalb der Klassendefinition Tests.
Wormer

Antworten:

72

Nach meinem Verständnis können in C ++ statische const-Mitglieder innerhalb einer Klasse definiert werden, sofern es sich um einen Integer-Typ handelt.

Du bist irgendwie richtig. Sie dürfen statische const-Integrale in der Klassendeklaration initialisieren, dies ist jedoch keine Definition.

http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/cplr038.htm

Interessanterweise kompiliert und verknüpft der Code, wenn ich den Aufruf von std :: min auskommentiere, einwandfrei (obwohl auf test :: N auch in der vorherigen Zeile verwiesen wird).

Irgendeine Idee, was los ist?

std :: min nimmt seine Parameter als const-Referenz. Wenn sie nach Wert genommen würden, hätten Sie dieses Problem nicht, aber da Sie eine Referenz benötigen, benötigen Sie auch eine Definition.

Hier ist Kapitel / Vers:

9.4.2 / 4 - Wenn ein staticDatenelement vom constIntegral- oder constAufzählungstyp ist, kann seine Deklaration in der Klassendefinition einen Konstanteninitialisierer angeben , der ein integraler Konstantenausdruck sein soll (5.19). In diesem Fall kann das Element in ganzzahligen konstanten Ausdrücken erscheinen. Das Mitglied muss weiterhin in einem Namespace-Bereich definiert sein, wenn es im Programm verwendet wird, und die Namespace-Bereichsdefinition darf keinen Initialisierer enthalten .

Eine mögliche Problemumgehung finden Sie in Chus Antwort.

Edward Strange
quelle
Ich verstehe, das ist interessant. Was ist in diesem Fall der Unterschied zwischen der Angabe des Werts zum Zeitpunkt der Deklaration und der Angabe des Werts zum Zeitpunkt der Definition? Welches wird empfohlen?
HighCommander4
Nun, ich glaube, dass Sie ohne Definition davonkommen können, solange Sie die Variable nie "verwenden". Wenn Sie es nur als Teil eines konstanten Ausdrucks verwenden, wird die Variable niemals verwendet. Ansonsten scheint es keinen großen Unterschied zu geben, außer dass Sie den Wert in der Kopfzeile sehen können - der möglicherweise das ist, was Sie wollen oder nicht.
Edward Strange
2
Die knappe Antwort lautet statisch const x = 1; ist ein Wert, aber kein Wert. Der Wert ist zur Kompilierungszeit als Konstante verfügbar (Sie können ein Array damit dimensionieren). Statische Konstante; [kein Initialisierer] muss in einer CPP-Datei definiert sein und kann entweder als r-Wert oder als l-Wert verwendet werden.
Dale Wilson
2
Es wäre schön, wenn sie dies erweitern / verbessern könnten. Initialisierte, aber nicht definierte Objekte sollten meiner Meinung nach genauso behandelt werden wie Literale. Zum Beispiel dürfen wir ein Literal 5an a binden const int&. Warum also nicht die OPs test::Nals das entsprechende Literal behandeln?
Aaron McDaid
Interessante Erklärung, danke! Dies bedeutet, dass in C ++ static const int immer noch kein Ersatz für Integer #defines ist. enum ist immer nur int signiert, daher muss man für einzelne Konstanten enum-Klassen verwenden. Es wäre für mich ziemlich offensichtlich, eine konstante Deklaration mit konstanten und bekannten Werten zu einer wörtlichen Konstante zu degenerieren, auf welche Weise dies ohne Probleme kompiliert werden würde. C ++ hat einen langen Weg vor sich ...
Johannes Overmann
51

Das Beispiel von Bjarne Stroustrup in seinen C ++ - FAQ legt nahe, dass Sie korrekt sind und nur dann eine Definition benötigen, wenn Sie die Adresse verwenden.

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

Er sagt : „Sie können die Adresse eines statischen Element nehmen , wenn (und nur dann) ist es eine out-of-Class - Definition hat“ . Was darauf hindeutet, dass es anders funktionieren würde. Vielleicht ruft Ihre Min-Funktion hinter den Kulissen Adressen auf.

HostileFork sagt, vertraue SE nicht
quelle
2
std::minnimmt seine Parameter als Referenz, weshalb eine Definition erforderlich ist.
Rakete1111
Wie würde ich die Definition schreiben, wenn AE eine Vorlagenklasse AE <Klasse T> ist und c7 kein int, sondern T :: size_type ist? Ich habe den Wert im Header auf "-1" initialisiert, aber clang sagt undefinierten Wert und ich weiß nicht, wie ich die Definition schreiben soll.
Fabian
@Fabian Ich bin auf Reisen und telefoniere und bin ein bisschen beschäftigt ... aber ich würde denken, dass Ihr Kommentar so klingt, als würde er am besten als neue Frage geschrieben. Schreiben Sie eine MCVE mit dem Fehler, den Sie erhalten, und geben Sie möglicherweise ein, was gcc sagt. Ich wette, die Leute würden dir schnell sagen, was was ist.
HostileFork sagt, vertraue SE
@ HostileFork: Wenn Sie eine MCVE schreiben, finden Sie die Lösung manchmal selbst heraus. In meinem Fall lautet die Antwort, dass template<class K, class V, class C> const typename AE<K,V,C>::KeyContainer::size_type AE<K,V,C>::c7;KeyContainer ein Typedef von std :: vector <K> ist. Man muss alle Vorlagenparameter auflisten und den Typnamen schreiben, da es sich um einen abhängigen Typ handelt. Vielleicht findet jemand diesen Kommentar hilfreich. Jetzt frage ich mich jedoch, wie ich dies in eine DLL exportieren soll, da sich die Vorlagenklasse natürlich in einem Header befindet. Muss ich c7 exportieren ???
Fabian
24

Eine andere Möglichkeit, dies für Ganzzahltypen zu tun, besteht darin, Konstanten als Aufzählungen in der Klasse zu definieren:

class test
{
public:
    enum { N = 10 };
};
Stephen Chu
quelle
2
Und das würde wahrscheinlich das Problem lösen. Wenn N als Parameter für min () verwendet wird, wird eine temporäre Variable erstellt, anstatt zu versuchen, auf eine angeblich vorhandene Variable zu verweisen.
Edward Strange
Dies hatte den Vorteil, dass es privat gemacht werden kann.
Agostino
11

Nicht nur int's. Sie können den Wert jedoch nicht in der Klassendeklaration definieren. Wenn Sie haben:

class classname
{
    public:
       static int const N;
}

In der .h-Datei müssen Sie dann haben:

int const classname::N = 10;

in der CPP-Datei.

Amardeep AC9MF
quelle
2
Mir ist bekannt, dass Sie eine Variable eines beliebigen Typs innerhalb der Klassendeklaration deklarieren können . Ich sagte, dass ich dachte, statische Ganzzahlkonstanten könnten auch innerhalb der Klassendeklaration definiert werden . Ist das nicht der Fall? Wenn nicht, warum gibt der Compiler in der Zeile, in der ich versuche, ihn innerhalb der Klasse zu definieren, keinen Fehler aus? Warum verursacht die Zeile std :: cout keinen Linkerfehler, die Zeile std :: min jedoch?
HighCommander4
Nein, statische Elemente in der Klassendeklaration können nicht definiert werden, da bei der Initialisierung Code ausgegeben wird. Im Gegensatz zu einer Inline-Funktion, die auch Code ausgibt, ist eine statische Definition global eindeutig.
Amardeep AC9MF
@ HighCommander4: Sie können einen Initialisierer für das static constIntegralelement in der Klassendefinition angeben . Aber das definiert dieses Mitglied immer noch nicht . Siehe Noah Roberts Antwort für Details.
AnT
9

Hier ist eine andere Möglichkeit, das Problem zu umgehen:

std::min(9, int(test::N));

(Ich denke, Crazy Eddies Antwort beschreibt richtig, warum das Problem besteht.)

Karadoc
quelle
5
oder sogarstd::min(9, +test::N);
Tomilov Anatoliy
Hier ist jedoch die große Frage: Ist das alles optimal? Ich weiß nichts über euch, aber meine große Anziehungskraft beim Überspringen der Definition ist, dass sie keinen Speicher und keinen Overhead bei der Verwendung der const static benötigt.
Opux
6

Ab C ++ 11 können Sie Folgendes verwenden:

static constexpr int N = 10;

Dies erfordert theoretisch immer noch, dass Sie die Konstante in einer CPP-Datei definieren, aber solange Sie nicht die Adresse Ndavon verwenden, ist es sehr unwahrscheinlich, dass eine Compiler-Implementierung einen Fehler erzeugt;).

Carlo Wood
quelle
Und was ist, wenn Sie den Wert wie im Beispiel als Argument vom Typ 'const int &' übergeben müssen? :-)
Wormer
Das funktioniert gut. Sie instanziieren N nicht auf diese Weise, sondern übergeben lediglich einen konstanten Verweis auf eine temporäre. wandbox.org/permlink/JWeyXwrVRvsn9cBj
Carlo Wood
C ++ 17 vielleicht nicht C ++ 14 und sogar nicht C ++ 17 in früheren Versionen von gcc 6.3.0 und niedriger, es ist keine Standardsache. Aber danke, dass du das erwähnt hast.
Wormer
Ah ja, du hast recht. Ich habe C ++ 14 auf der Zauberbox nicht ausprobiert. Na ja, das ist der Teil, in dem ich sagte "Das erfordert theoretisch immer noch, dass Sie die Konstante definieren". Sie haben also Recht, dass es nicht "Standard" ist.
Carlo Wood
3

Mit C ++ können statische const-Mitglieder innerhalb einer Klasse definiert werden

Nein, 3.1 §2 sagt:

Eine Deklaration ist eine Definition, es sei denn, sie deklariert eine Funktion ohne Angabe des Funktionskörpers (8.4), enthält den externen Bezeichner (7.1.1) oder eine Verknüpfungsspezifikation (7.5) und deklariert weder einen Initialisierer noch einen Funktionskörper statische Daten Mitglied in einer Klassendefinition (9.4), es ist eine Klassennamendeklaration (9.1), es ist eine opake-enum-Deklaration (7.2) oder es ist eine typedef-Deklaration (7.1.3), eine using-Deklaration (7.3). 3) oder eine Verwendungsrichtlinie (7.3.4).

Fredoverflow
quelle