Definizione di membri interi const statici nella definizione della classe


109

La mia comprensione è che C ++ consente di definire membri const statici all'interno di una classe purché sia ​​un tipo intero.

Perché, allora, il codice seguente mi dà un errore del linker?

#include <algorithm>
#include <iostream>

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

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

L'errore che ottengo è:

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

È interessante notare che, se commento la chiamata a std :: min, il codice si compila e si collega correttamente (anche se test :: N è indicato anche nella riga precedente).

Qualche idea su cosa sta succedendo?

Il mio compilatore è gcc 4.4 su Linux.


3
Funziona bene su Visual Studio 2010.
Puppy

4
Questo errore esatto è spiegato su gcc.gnu.org/wiki/…
Jonathan Wakely

Nel caso particolare di char, puoi definirlo invece come constexpr static const char &N = "n"[0];. Nota il &. Immagino che funzioni perché le stringhe letterali vengono definite automaticamente. Sono un po 'preoccupato per questo però - potrebbe comportarsi in modo strano in un file di intestazione tra diverse unità di traduzione, poiché la stringa sarà probabilmente a più indirizzi diversi.
Aaron McDaid

1
Questa domanda è un manifesto di quanto sia scarsa la risposta C ++ a "non usare #defines per le costanti".
Johannes Overmann

1
@JohannesOvermann A questo proposito, voglio menzionare l'uso di inline per variabili globali a partire da C ++ 17 inline const int N = 10, che per quanto ne so ha ancora una memoria da qualche parte definita dal linker. La parola chiave inline potrebbe anche essere utilizzata in questo caso per fornire la definizione di variabile statica all'interno del test di definizione della classe.
Wormer

Risposte:


72

La mia comprensione è che C ++ consente di definire membri const statici all'interno di una classe purché sia ​​un tipo intero.

Hai ragione. È consentito inizializzare gli integrali cost statici nella dichiarazione della classe, ma questa non è una definizione.

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

È interessante notare che, se commento la chiamata a std :: min, il codice si compila e si collega correttamente (anche se test :: N è indicato anche nella riga precedente).

Qualche idea su cosa sta succedendo?

std :: min prende i suoi parametri per riferimento const. Se li prendessi per valore non avresti questo problema ma dato che hai bisogno di un riferimento hai bisogno anche di una definizione.

Ecco il capitolo / verso:

9.4.2 / 4 - Se un staticmembro di dati è di tipo constintegrale o constenumerazione, la sua dichiarazione nella definizione di classe può specificare un inizializzatore di costante che sarà un'espressione di costante integrale (5.19). In tal caso, il membro può apparire in espressioni di costanti integrali. Il membro deve essere ancora definito in un ambito dello spazio dei nomi se viene utilizzato nel programma e la definizione dell'ambito dello spazio dei nomi non deve contenere un inizializzatore .

Vedi la risposta di Chu per una possibile soluzione alternativa.


Capisco, è interessante. In tal caso, qual è la differenza tra fornire il valore al punto della dichiarazione e fornire il valore al punto della definizione? Quale è consigliato?
HighCommander4

Beh, credo che tu possa cavartela senza una definizione fintanto che non "usi" mai la variabile. Se lo usi solo come parte di un'espressione costante, la variabile non viene mai utilizzata. Altrimenti non sembra esserci un'enorme differenza oltre alla possibilità di vedere il valore nell'intestazione, che può essere o meno quello che vuoi.
Edward Strange,

2
La risposta concisa è const statico x = 1; è un rvalue ma non un lvalue. Il valore è disponibile come costante in fase di compilazione (è possibile dimensionare un array con esso) static const y; [nessun inizializzatore] deve essere definito in un file cpp e può essere usato sia come rvalue che come lvalue.
Dale Wilson

2
Sarebbe bello se potessero estendere / migliorare questo. Gli oggetti inizializzati ma non definiti dovrebbero, a mio parere, essere trattati allo stesso modo dei letterali. Ad esempio, ci è consentito associare un letterale 5a un file const int&. Allora perché non trattare gli OP test::Ncome il corrispondente letterale?
Aaron McDaid

Spiegazione interessante, grazie! Ciò significa che in C ++ static const int non sostituisce ancora il numero intero #defines. enum è sempre solo int firmato, quindi è necessario utilizzare le classi enum per le singole costanti. Sarebbe abbastanza ovvio per me degenerare una dichiarazione costante con valori costanti e conosci a una costante letterale, in questo modo si compilerebbe senza problemi. Il C ++ ha ancora
molta

51

L'esempio di Bjarne Stroustrup nelle sue FAQ in C ++ suggerisce che hai ragione e che hai bisogno di una definizione solo se prendi l'indirizzo.

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
    // ...
}

Dice "Puoi prendere l'indirizzo di un membro statico se (e solo se) ha una definizione fuori classe" . Il che suggerisce che funzionerebbe altrimenti. Forse la tua funzione min richiama in qualche modo indirizzi dietro le quinte.


2
std::minprende i suoi parametri per riferimento, motivo per cui è necessaria una definizione.
Rakete 1111

Come scriverei la definizione se AE è una classe template AE <class T> e c7 non è un int ma T :: size_type? Ho il valore inizializzato a "-1" nell'intestazione ma clang dice un valore indefinito e non so come scrivere la definizione.
Fabian

@Fabian Sono in viaggio e su un telefono e un po 'occupato ... ma penso che il tuo commento suona come se fosse meglio scritto come una nuova domanda. Scrivi un MCVE che includa l'errore che ottieni, magari anche inserire ciò che dice gcc. Scommetto che la gente ti direbbe velocemente cosa è cosa.
HostileFork dice di non fidarsi di SE

@HostileFork: quando scrivi un MCVE, a volte trovi tu stesso la soluzione. Per il mio caso la risposta è template<class K, class V, class C> const typename AE<K,V,C>::KeyContainer::size_type AE<K,V,C>::c7;dove KeyContainer è un typedef di std :: vector <K>. È necessario elencare tutti i parametri del modello e scrivere il nome del tipo perché è un tipo dipendente. Forse qualcuno troverà utile questo commento. Tuttavia, ora mi chiedo come esportarlo in una DLL perché la classe template è ovviamente in un'intestazione. Devo esportare c7 ???
Fabian

24

Un altro modo per farlo, comunque per i tipi interi, è definire le costanti come enumerazioni nella classe:

class test
{
public:
    enum { N = 10 };
};

2
E questo probabilmente risolverebbe il problema. Quando N viene utilizzato come parametro per min (), verrà creato un elemento temporaneo anziché tentare di fare riferimento a una variabile presumibilmente esistente.
Edward Strange,

Ciò ha avuto il vantaggio di poter essere reso privato.
Agostino

11

Non solo int. Ma non puoi definire il valore nella dichiarazione della classe. Se hai:

class classname
{
    public:
       static int const N;
}

nel file .h allora devi avere:

int const classname::N = 10;

nel file .cpp.


2
Sono consapevole che puoi dichiarare una variabile di qualsiasi tipo all'interno della dichiarazione della classe. Ho detto che pensavo che le costanti intere statiche potessero essere definite anche all'interno della dichiarazione della classe. Non è questo il caso? In caso contrario, perché il compilatore non dà un errore alla riga in cui provo a definirlo all'interno della classe? Inoltre, perché la riga std :: cout non causa un errore del linker, ma la riga std :: min sì?
HighCommander4

No, non è possibile definire membri statici nella dichiarazione della classe perché l'inizializzazione genera codice. A differenza di una funzione inline che emette anche codice, una definizione statica è globalmente unica.
Amardeep AC9MF

@ HighCommander4: è possibile fornire un inizializzatore per il static constmembro integrale nella definizione della classe. Ma questo ancora non definisce quel membro. Vedi la risposta di Noah Roberts per i dettagli.
AnT

9

Ecco un altro modo per aggirare il problema:

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

(Penso che la risposta di Crazy Eddie descriva correttamente perché il problema esiste.)


5
o anchestd::min(9, +test::N);
Tomilov Anatoliy

Ma ecco la grande domanda: tutto questo è ottimale? Non so voi ragazzi, ma la mia grande attrazione nel saltare la definizione è che non dovrebbe occupare memoria e nessun sovraccarico nell'uso della statica const.
Opux

6

A partire da C ++ 11 puoi usare:

static constexpr int N = 10;

Questo teoricamente richiede ancora di definire la costante in un file .cpp, ma fintanto che non ne prendi l'indirizzo Nè molto improbabile che qualsiasi implementazione del compilatore produca un errore;).


E se fosse necessario passare il valore come argomento di tipo "const int &" come nell'esempio? :-)
Wormer

Funziona bene. Non stai istanziando N in questo modo, semplicemente passando un riferimento const a un temporaneo. wandbox.org/permlink/JWeyXwrVRvsn9cBj
Carlo Wood

C ++ 17 forse, non C ++ 14, e anche non C ++ 17 nelle versioni precedenti di gcc 6.3.0 e inferiori, non è una cosa standard. Ma grazie per averlo detto.
Wormer

Ah sì, hai ragione. Non ho provato c ++ 14 su wandbox. Vabbè, questa è la parte in cui ho detto "Questo teoricamente richiede ancora di definire la costante". Quindi, hai ragione che non è "standard".
Carlo Wood

3

C ++ consente di definire membri const statici all'interno di una classe

No, 3.1 §2 dice:

Una dichiarazione è una definizione a meno che non dichiari una funzione senza specificare il corpo della funzione (8.4), contiene lo specificatore extern (7.1.1) o una specifica di collegamento (7.5) e né un inizializzatore né un corpo di funzione, dichiara un dato statico membro in una definizione di classe (9.4), è una dichiarazione del nome di una classe (9.1), è una dichiarazione-enum-opaca (7.2), o è una dichiarazione typedef (7.1.3), una dichiarazione-using (7.3. 3) o una direttiva sull'uso (7.3.4).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.