Définition de membres entiers const statiques dans la définition de classe

109

Je crois comprendre que C ++ permet aux membres const statiques d'être définis à l'intérieur d'une classe tant qu'il s'agit d'un type entier.

Pourquoi, alors, le code suivant me donne-t-il une erreur de l'éditeur de liens?

#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'erreur que j'obtiens est:

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

Fait intéressant, si je commente l'appel à std :: min, le code se compile et se lie très bien (même si test :: N est également référencé sur la ligne précédente).

Une idée de ce qui se passe?

Mon compilateur est gcc 4.4 sous Linux.

HighCommander4
la source
3
Fonctionne très bien sur Visual Studio 2010.
Puppy
4
Cette erreur exacte est expliquée sur gcc.gnu.org/wiki/…
Jonathan Wakely
Dans le cas particulier de char, vous pouvez le définir à la place comme constexpr static const char &N = "n"[0];. Notez le &. Je suppose que cela fonctionne parce que les chaînes littérales sont définies automatiquement. Je suis un peu inquiet à ce sujet cependant - il pourrait se comporter étrangement dans un fichier d'en-tête parmi différentes unités de traduction, car la chaîne sera probablement à plusieurs adresses différentes.
Aaron McDaid du
1
Cette question montre à quel point la réponse C ++ à «ne pas utiliser #defines pour les constantes» est toujours médiocre.
Johannes Overmann
1
@JohannesOvermann À cet égard, je veux mentionner l'utilisation de l'inline pour les variables globales depuis C ++ 17 inline const int N = 10, qui à ma connaissance a encore un stockage quelque part défini par l'éditeur de liens. Le mot clé en ligne pourrait également être utilisé dans ce cas pour fournir une définition de variable statique dans le test de définition de classe.
Wormer

Réponses:

72

Je crois comprendre que C ++ permet aux membres const statiques d'être définis à l'intérieur d'une classe tant qu'il s'agit d'un type entier.

Vous avez en quelque sorte raison. Vous êtes autorisé à initialiser des intégrales const statiques dans la déclaration de classe, mais ce n'est pas une définition.

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

Fait intéressant, si je commente l'appel à std :: min, le code se compile et se lie très bien (même si test :: N est également référencé sur la ligne précédente).

Une idée de ce qui se passe?

std :: min prend ses paramètres par référence const. Si cela les prenait par valeur, vous n'auriez pas ce problème, mais comme vous avez besoin d'une référence, vous avez également besoin d'une définition.

Voici le chapitre / verset:

9.4.2 / 4 - Si un staticmembre de données est de type constintégral ou consténumération, sa déclaration dans la définition de classe peut spécifier un initialiseur de constante qui doit être une expression constante intégrale (5.19). Dans ce cas, le membre peut apparaître dans des expressions constantes intégrales. Le membre doit toujours être défini dans une portée d'espace de noms s'il est utilisé dans le programme et la définition de portée d'espace de noms ne doit pas contenir d' initialiseur .

Voir la réponse de Chu pour une solution de contournement possible.

Edward étrange
la source
Je vois, c'est intéressant. Dans ce cas, quelle est la différence entre fournir la valeur au point de déclaration et fournir la valeur au point de définition? Lequel est recommandé?
HighCommander4
Eh bien, je crois que vous pouvez vous en tirer sans définition tant que vous n'utilisez jamais réellement la variable. Si vous ne l'utilisez que dans le cadre d'une expression constante, la variable n'est jamais utilisée. Sinon, il ne semble pas y avoir une énorme différence en plus de pouvoir voir la valeur dans l'en-tête - qui peut ou non être ce que vous voulez.
Edward Strange
2
La réponse laconique est statique const x = 1; est une rvalue mais pas une lvalue. La valeur est disponible sous forme de constante au moment de la compilation (vous pouvez dimensionner un tableau avec elle) static const y; [no initializer] doit être défini dans un fichier cpp et peut être utilisé soit comme rvalue soit comme lvalue.
Dale Wilson
2
Ce serait bien s'ils pouvaient étendre / améliorer cela. Les objets initialisés mais non définis devraient, à mon avis, être traités de la même manière que les littéraux. Par exemple, nous sommes autorisés à lier un littéral 5à un const int&. Alors pourquoi ne pas traiter les OP test::Ncomme le littéral correspondant?
Aaron McDaid du
Explication intéressante, merci! Cela signifie qu'en C ++ static const int ne remplace toujours pas les entiers #defines. enum est toujours uniquement signé int, il faut donc utiliser des classes enum pour les constantes individuelles. Il serait tout à fait évident pour moi de dégénérer une déclaration constante avec des valeurs constantes et connues en une constante littérale dans laquelle cela se compilerait sans problèmes. Le C ++ a un long chemin à parcourir ...
Johannes Overmann
51

L'exemple de Bjarne Stroustrup dans sa FAQ C ++ suggère que vous avez raison et que vous n'avez besoin d'une définition que si vous prenez l'adresse.

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

Il dit "Vous pouvez prendre l'adresse d'un membre statique si (et seulement si) il a une définition hors classe" . Ce qui suggère que cela fonctionnerait autrement. Peut-être que votre fonction min invoque en quelque sorte des adresses dans les coulisses.

HostileFork dit de ne pas faire confiance à SE
la source
2
std::minprend ses paramètres par référence, c'est pourquoi une définition est requise.
Rakete1111
Comment écrirais-je la définition si AE est une classe de modèle AE <class T> et c7 n'est pas un int mais T :: size_type? J'ai la valeur initialisée à "-1" dans l'en-tête mais clang indique une valeur indéfinie et je ne sais pas comment écrire la définition.
Fabian
@Fabian Je voyage et au téléphone et un peu occupé ... mais je pense que votre commentaire semble qu'il serait préférable d'écrire comme une nouvelle question. Écrivez un MCVE en incluant l'erreur que vous obtenez, peut-être aussi ajoutez ce que dit gcc. Je parie que les gens vous diront rapidement ce qui est quoi.
HostileFork dit de ne pas faire confiance à SE
@HostileFork: Lorsque vous écrivez un MCVE, vous trouvez parfois la solution vous-même. Pour mon cas, la réponse est template<class K, class V, class C> const typename AE<K,V,C>::KeyContainer::size_type AE<K,V,C>::c7;où KeyContainer est un typedef de std :: vector <K>. Il faut lister tous les paramètres du modèle et écrire le nom du type car c'est un type dépendant. Peut-être que quelqu'un trouvera ce commentaire utile. Cependant, maintenant je me demande comment exporter cela dans une DLL car la classe de modèle est bien sûr dans un en-tête. Dois-je exporter c7 ???
Fabian
24

Une autre façon de faire cela, pour les types entiers de toute façon, est de définir des constantes comme des énumérations dans la classe:

class test
{
public:
    enum { N = 10 };
};
Stephen Chu
la source
2
Et cela résoudrait probablement le problème. Lorsque N est utilisé comme paramètre pour min (), cela provoquera la création d'un temporaire plutôt que d'essayer de faire référence à une variable supposée existante.
Edward Strange
Cela avait l'avantage de pouvoir être rendu privé.
Agostino
11

Pas seulement des int. Mais vous ne pouvez pas définir la valeur dans la déclaration de classe. Si tu as:

class classname
{
    public:
       static int const N;
}

dans le fichier .h, vous devez avoir:

int const classname::N = 10;

dans le fichier .cpp.

Amardeep AC9MF
la source
2
Je suis conscient que vous pouvez déclarer une variable de n'importe quel type dans la déclaration de classe. J'ai dit que je pensais que les constantes entières statiques pouvaient également être définies dans la déclaration de classe. Ce n'est pas le cas? Sinon, pourquoi le compilateur ne donne-t-il pas d'erreur à la ligne où j'essaye de le définir à l'intérieur de la classe? De plus, pourquoi la ligne std :: cout ne provoque-t-elle pas une erreur de l'éditeur de liens, contrairement à la ligne std :: min?
HighCommander4
Non, impossible de définir des membres statiques dans la déclaration de classe car l'initialisation émet du code. Contrairement à une fonction en ligne qui émet également du code, une définition statique est globalement unique.
Amardeep AC9MF
@ HighCommander4: vous pouvez fournir un initialiseur pour le static const membre intégral dans la définition de classe. Mais cela ne définit toujours pas ce membre. Voir la réponse de Noah Roberts pour plus de détails.
Du
9

Voici une autre façon de contourner le problème:

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

(Je pense que la réponse de Crazy Eddie décrit correctement pourquoi le problème existe.)

Karadoc
la source
5
ou mêmestd::min(9, +test::N);
Tomilov Anatoliy
Voici la grande question cependant: tout cela est-il optimal? Je ne sais pas pour vous les gars, mais mon grand intérêt à sauter la définition est que cela ne devrait pas prendre de mémoire et pas de surcharge en utilisant le const static.
Opux
6

À partir de C ++ 11, vous pouvez utiliser:

static constexpr int N = 10;

Cela vous oblige théoriquement toujours à définir la constante dans un fichier .cpp, mais tant que vous n'en prenez pas l'adresse, Nil est très peu probable qu'une implémentation du compilateur produise une erreur;).

Carlo Wood
la source
Et si vous avez besoin de passer la valeur comme argument de type 'const int &' comme dans l'exemple? :-)
Wormer
Cela fonctionne très bien. Vous n'êtes pas instanciation N cette façon, simplement passer une référence const à un temporaire. wandbox.org/permlink/JWeyXwrVRvsn9cBj
Carlo Wood
C ++ 17 peut-être, pas C ++ 14, et même pas C ++ 17 dans les versions antérieures de gcc 6.3.0 et inférieurs, ce n'est pas une chose standard. Mais merci d'avoir mentionné cela.
Wormer
Ah oui, tu as raison. Je n'ai pas essayé C ++ 14 sur wandbox. Eh bien, c'est la partie où j'ai dit "Cela nécessite encore théoriquement de définir la constante". Donc, vous avez raison de dire que ce n'est pas «standard».
Carlo Wood
3

C ++ permet aux membres const statiques d'être définis à l'intérieur d'une classe

Non, 3.1 §2 dit:

Une déclaration est une définition à moins qu'elle ne déclare une fonction sans spécifier le corps de la fonction (8.4), elle contient le spécificateur extern (7.1.1) ou une spécification de liaison (7.5) et ni un initialiseur ni un corps de fonction, elle déclare une donnée statique membre d'une définition de classe (9.4), c'est une déclaration de nom de classe (9.1), c'est une déclaration opaque-enum (7.2), ou c'est une déclaration typedef (7.1.3), une déclaration d'utilisation (7.3. 3), ou une directive d'utilisation (7.3.4).

fredoverflow
la source