web-dev-qa-db-fra.com

Comment initialiser des membres statiques dans l'en-tête

Given est une classe avec un membre statique.

class BaseClass
{
public:
    static std::string bstring;
};

La chaîne doit évidemment être initialisée par défaut en dehors de la classe.

std::string BaseClass::bstring {"."};

Si j'inclus la ligne ci-dessus dans l'en-tête avec la classe, j'obtiens un symbol multiply defined Erreur. Il doit être défini dans un fichier cpp distinct, même avec include guards ou pragma once.

N'y a-t-il pas un moyen de le définir dans l'en-tête?

34
Appleshell

Vous ne pouvez pas définir plus d'une fois une variable membre static. Si vous mettez des définitions de variables dans un en-tête, elles seront définies dans chaque unité de traduction où l'en-tête est inclus. Étant donné que les gardes d'inclusion n'affectent que la compilation d'une unité de traduction, ils ne seront pas utiles non plus.

Cependant, vous pouvez définir static membre fonctions! Maintenant, à première vue, cela peut ne pas sembler utile, sauf que, bien sûr, cette fonction peut avoir une variable locale static et renvoyer une référence à l'un de ces comportements se comporte presque comme un membre static variable:

static std::string& bstring() { static std::string rc{"."}; return rc; }

La variable locale static sera initialisée lors du premier appel de cette fonction. Autrement dit, la construction est retardée jusqu'à ce que la fonction soit accédée la première fois. Bien sûr, si vous utilisez cette fonction pour initialiser d'autres objets globaux, vous pouvez également vous assurer que l'objet est construit dans le temps. Si vous utilisez plusieurs threads, cela peut ressembler à une course de données potentielle, mais ce n'est pas le cas (sauf si vous utilisez C++ 03): l'initialisation de la fonction local static variable est thread-safe.

56
Dietmar Kühl

En ce qui concerne

N'existe-t-il pas un moyen de définir [le membre de données statiques] dans l'en-tête?

Oui il y a.

template< class Dummy >
struct BaseClass_statics
{
    static std::string bstring;
};

template< class Dummy >
std::string BaseClass_statics<Dummy>::bstring = ".";

class BaseClass
    : public BaseClass_statics<void>
{};

Une alternative consiste à utiliser une fonction, comme l'a suggéré Dietmar. Il s'agit essentiellement d'un singleton de Meyers (google it).

Edit : De plus, depuis que cette réponse a été publiée, nous avons la proposition d'objet en ligne, qui je pense est acceptée pour C++ 17.

Quoi qu'il en soit, réfléchissez-y à deux fois à propos du design ici. Les variables globales sont Evil ™. Il s'agit essentiellement d'un global.

10

En C++ 17, vous pouvez utiliser les variables en ligne , que vous pouvez utiliser même à l'extérieur Des classes.

Le spécificateur en ligne, lorsqu'il est utilisé dans un seq-spécificateur-seq d'une variable avec une durée de stockage statique (membre de classe statique ou variable de portée d'espace de noms), déclare que la variable est une variable en ligne.

Une variable membre statique (mais pas une variable de portée d'espace de noms) déclarée constexpr est implicitement une variable en ligne.

Par exemple:

class Someclass {
public:
    inline static int someVar = 1;
};

Ou,

namespace SomeNamespace {
    inline static int someVar = 1;
}

⁽¹⁾ https://en.cppreference.com/w/cpp/language/inline

5

Pour conserver la définition d'une valeur statique avec la déclaration en C++ 11, une structure statique imbriquée peut être utilisée. Dans ce cas, le membre statique est une structure et doit être défini dans un fichier .cpp, mais les valeurs sont dans l'en-tête.

class BaseClass
{
public:
  static struct _Static {
     std::string bstring {"."};
  } global;
};

Au lieu d'initialiser des membres individuels, toute la structure statique est initialisée:

BaseClass::_Static BaseClass::global;

Les valeurs sont accessibles avec

BaseClass::global.bstring;

Notez que cette solution souffre toujours du problème de l'ordre d'initialisation des variables statiques. Lorsqu'une valeur statique est utilisée pour initialiser une autre variable statique, la première peut ne pas encore être initialisée.

// file.h
class File {
public:
  static struct _Extensions {
    const std::string h{ ".h" };
    const std::string hpp{ ".hpp" };
    const std::string c{ ".c" };
    const std::string cpp{ ".cpp" };
  } extension;
};

// file.cpp
File::_Extensions File::extension;

// module.cpp
static std::set<std::string> headers{ File::extension.h, File::extension.hpp };

Dans ce cas, la variable statique en-têtes contiendra {""} ou {".h", ".hpp"}, selon l'ordre d'initialisation créé par l'éditeur de liens.

4
Marko Mahnič

Non, cela ne peut pas être fait dans un en-tête - du moins pas si l'en-tête est inclus plus d'une fois dans vos fichiers source, ce qui semble être le cas, ou vous n'obtiendrez pas une erreur comme celle-ci. Il suffit de le coller dans l'un des fichiers .cpp et d'en finir.

3
Mats Petersson

§3.2.6 et les paragraphes suivants du brouillon c ++ 17 actuel (n4296) définissent les règles lorsque plusieurs définitions peuvent être présentes dans différentes unités de traduction:

Il peut y avoir plusieurs définitions d'un type de classe (article 9), type d'énumération (7.2), fonction en ligne avec liaison externe (7.1.2), modèle de classe (article 14), modèle de fonction non statique (14.5.6) , membre de données statiques d'un modèle de classe (14.5.1.3), fonction membre d'un modèle de classe (14.5.1.1) ou spécialisation de modèle pour laquelle certains paramètres de modèle ne sont pas spécifiés (14.7, 14.5.5) dans un programme à condition que chacun la définition apparaît dans une unité de traduction différente et à condition que les définitions satisfassent aux exigences suivantes. Étant donné une telle entité nommée D définie dans plus d'une unité de traduction, alors [...]

De toute évidence, les définitions de membres de données statiques de type classe ne sont pas considérées comme apparaissant dans plusieurs unités de traduction. Ainsi, selon la norme, ce n'est pas autorisé.

Les réponses suggérées de Cheers et hth. - Alf et Dietmar sont plus une sorte de "hack", exploitant que les définitions de

membre de données statiques d'un modèle de classe (14.5.1.3)

et

fonction en ligne avec liaison externe (7.1.2)

sont autorisés dans plusieurs TU (FYI: les fonctions statiques définies dans une définition de classe ont un lien externe et sont implicitement définies comme inline).

3
SebNag

MISE À JOUR: Ma réponse ci-dessous explique pourquoi cela ne peut pas être fait de la manière suggérée par la question. Il y a au moins deux réponses pour contourner cela; ils peuvent ou non résoudre le problème.


Le membre statique bstring doit être lié à une adresse mémoire spécifique. Pour cela, il doit apparaître dans un seul fichier objet, il doit donc apparaître dans un seul fichier cpp. Sauf si vous jouez avec #ifdef pour vous assurer que cela se produit, ce que vous voulez ne peut pas être fait dans le fichier d'en-tête, car votre fichier d'en-tête peut être inclus par plusieurs fichiers cpp.

1
nickie