Initialisation du membre statique C ++ (modèle amusant à l’intérieur)

Pour l’initialisation des membres statiques, j’utilise une structure d’assistance nestede, qui fonctionne bien pour les classes non basées sur des modèles. Cependant, si la classe englobante est paramétrée par un modèle, la classe d’initialisation nestede n’est pas instanciée si l’object d’assistance n’est pas accessible dans le code principal. A titre d’illustration, un exemple simplifié (dans mon cas, je dois initialiser un vecteur).

#include  #include  struct A { struct InitHelper { InitHelper() { A::mA = "Hello, I'm A."; } }; static std::ssortingng mA; static InitHelper mInit; static const std::ssortingng& getA(){ return mA; } }; std::ssortingng A::mA; A::InitHelper A::mInit; template struct B { struct InitHelper { InitHelper() { B::mB = "Hello, I'm B."; // [3] } }; static std::ssortingng mB; static InitHelper mInit; static const std::ssortingng& getB() { return mB; } static InitHelper& getHelper(){ return mInit; } }; template std::ssortingng B::mB; //[4] template typename B::InitHelper B::mInit; int main(int argc, char* argv[]) { std::cout << "A = " << A::getA() << std::endl; // std::cout << "B = " << B::getB() << std::endl; // [1] // B::getHelper(); // [2] } 

Avec g ++ 4.4.1:

Donc ma question: Est-ce un bug de compilateur ou est-ce que le bogue est entre le moniteur et la chaise? Et si tel est le cas: existe-t-il une solution élégante (c’est-à-dire sans appeler explicitement une méthode d’initialisation statique)?

Mise à jour I:
Cela semble être un comportement souhaité (tel que défini dans la norme ISO / IEC C ++ 2003, 14.7.1):

À moins qu’un membre d’un modèle de classe ou d’un modèle de membre n’ait été explicitement instancié ou explicitement spécialisé, la spécialisation du membre est implicitement instanciée lorsque la spécialisation est référencée dans un contexte qui requirejs la définition du membre; en particulier, l’initialisation (et tous les effets secondaires associés) d’un membre de données statique ne se produit que si le membre de données statique est lui-même utilisé d’une manière qui requirejs la définition de la donnée membre statique.

Cela a été discuté sur usenet il y a quelque temps, alors que j’essayais de répondre à une autre question sur stackoverflow: Point of Instantiation of Static Data Members . Je pense que cela vaut la peine de réduire le test-case et d’envisager chaque scénario isolément. Voyons donc d’abord de manière plus générale:


 struct C { C(int n) { printf("%d\n", n); } }; template struct A { static C c; }; template C A::c(N); A<1> a; // implicit instantiation of A<1> and 2 A<2> b; 

Vous avez la définition d’un modèle de membre de données statique. Cela ne crée pas encore de membres de données, à cause de 14.7.1 :

“… en particulier, l’initialisation (et tous les effets secondaires associés) d’un membre de données statique ne se produit que si le membre de données statique est lui-même utilisé d’une manière qui nécessite la définition de la donnée membre statique.”

La définition de quelque chose (= entité) est nécessaire lorsque cette entité est “utilisée”, selon la règle de définition qui définit ce mot (en 3.2/2 ). En particulier, si toutes les références proviennent de modèles non étayés, de membres d’un modèle ou d’une sizeof ou d’éléments similaires qui “n’utilisent” pas l’entité (puisqu’ils ne l’évaluent pas ou n’existent pas encore comme fonctions / fonctions membres qui sont elles-mêmes utilisées), un tel membre de données statique n’est pas instancié.

Une instanciation implicite par 14.7.1/7 instancie des déclarations de membres de données statiques – c’est-à-dire qu’elle instanciera tout modèle nécessaire au traitement de cette déclaration. Cependant, il n’instanciera pas les définitions – c’est-à-dire que les initialiseurs ne sont pas instanciés et que les constructeurs du type de ces données statiques ne sont pas implicitement définis (marqués comme utilisés).

Cela signifie que le code ci-dessus ne produira rien pour le moment. Faisons maintenant des instanciations implicites des membres de données statiques.

 int main() { A<1>::c; // reference them A<2>::c; } 

Cela entraînera l’existence des deux membres de données statiques, mais la question est la suivante: comment est l’ordre d’initialisation? Sur une simple lecture, on pourrait penser que 3.6.2/1 s’applique, ce qui dit:

“Les objects avec une durée de stockage statique définie dans la scope de l’espace de noms dans la même unité de traduction et initialisés de manière dynamic doivent être initialisés dans l’ordre dans lequel leur définition apparaît dans l’unité de traduction.”

Maintenant, comme indiqué dans le post usenet et expliqué dans ce rapport de défaut , ces membres de données statiques ne sont pas définis dans une unité de traduction, mais ils sont instanciés dans une unité d’instanciation , comme expliqué en 2.1/1 :

Chaque unité de traduction traduite est examinée pour produire une liste des instanciations requirejses. [Note: ceci peut inclure des instanciations qui ont été explicitement demandées (14.7.2). ] Les définitions des modèles requirejs sont localisées. La mise en œuvre indique si la source des unités de traduction contenant ces définitions doit être disponible. [Remarque: une implémentation peut encoder des informations suffisantes dans l’unité de traduction traduite afin de s’assurer que la source n’est pas requirejse ici. ] Toutes les instanciations requirejses sont effectuées pour produire des unités d’instanciation. [Remarque: ces unités sont similaires aux unités de traduction traduites, mais ne contiennent aucune référence à des modèles non étayés et à aucune définition de modèle. ] Le programme est mal formé si une instanciation échoue.

Le point d’instanciation d’un tel membre n’a pas non plus d’importance, car un tel point d’instanciation est le lien contextuel entre une instanciation et ses unités de traduction – il définit les déclarations visibles (comme spécifié au 14.6.4.1 , et chacune des ces instanciations doivent donner la même signification aux instanciations, comme spécifié dans la règle à une définition à 3.2/5 , dernier point).

Si nous voulons une initialisation ordonnée, nous devons nous arranger pour éviter les instanciations, mais avec des déclarations explicites – il s’agit du domaine des spécialisations explicites, car elles ne sont pas vraiment différentes des déclarations normales. En fait, C ++ 0x a modifié son libellé de 3.6.2 comme suit:

L’initialisation dynamic d’un object non local avec une durée de stockage statique est ordonnée ou non. Les définitions des membres de données statiques de modèle de classe explicitement spécialisés ont ordonné l’initialisation. D’autres membres de données statiques d’un modèle de classe (c’est-à-dire, des spécialisations implicitement ou explicitement instanciées) ont une initialisation non ordonnée.


Cela signifie pour votre code que:

  • [1] et [2] commenté: Il n’existe aucune référence aux membres de données statiques, donc leurs définitions (et non pas leurs déclarations, car l’instanciation de B n’est pas nécessaire) ne sont pas instanciées. Aucun effet secondaire ne se produit.
  • [1] commentaire: B::getB() est utilisé, ce qui en lui-même utilise B::mB , ce qui nécessite l’existence de ce membre statique. La chaîne est initialisée avant main (en tout cas avant cette instruction, dans le cadre de l’initialisation d’objects non locaux). Rien n’utilise B::mInit , il n’est donc pas instancié, donc aucun object de B::InitHelper n’est créé, ce qui rend son constructeur inutilisé, ce qui n’atsortingbuera jamais quelque chose à B::mB : Vous allez simplement sortir une chaîne vide.
  • [1] and [2] commentaire: Que cela a fonctionné pour vous, c’est de la chance (ou le contraire :)). Il n’y a pas d’exigence pour un ordre particulier d’appels d’initialisation, comme expliqué ci-dessus. Cela pourrait fonctionner sur VC ++, échouer sur GCC et travailler sur clang. Nous ne soaps pas
  • [1] commenté, [2] commentaire: Même problème – encore une fois, les deux membres de données statiques sont utilisés : B::mInit est utilisé par B::getHelper , et l’instanciation de B::mInit provoquera l’instanciation de son constructeur, qui utilisera B::mB – mais pour votre compilateur, l’ordre est différent dans cette exécution (le comportement non spécifié n’est pas obligatoirement cohérent entre les différentes exécutions): il initialise B::mInit premier, qui fonctionnera sur un object de chaîne non encore construit.

Le problème est que les définitions que vous donnez pour les variables membres statiques sont également des modèles.

 template std::ssortingng B::mB; template typename B::InitHelper B::mInit; 

Pendant la compilation, cela ne définit rien, puisque T n’est pas connu. C’est quelque chose comme une déclaration de classe ou une définition de modèle, le compilateur ne génère pas de code ou ne réserve pas de stockage lorsqu’il le voit.

La définition se produit implicitement plus tard, lorsque vous utilisez la classe de modèle. Parce que dans le cas de segfaulting, vous n’utilisez pas B :: mInit, il n’est jamais créé.

Une solution serait de définir explicitement le membre nécessaire (sans l’initialiser): Mettez un fichier source quelque part

 template<> typename B::InitHelper B::mInit; 

Cela fonctionne essentiellement de la même manière que la définition explicite d’une classe de modèle.

  • [1] cas non commenté: ça va. static InitHelper B::mInit n’existe pas. Si le membre de la classe de template (struct) n’est pas utilisé, il n’est pas compilé.

  • [1] and [2] cas non commenté: ça va. B::getHelper() utilise static InitHelper B::mInit et mInit existent.

  • [1] a commenté, [2] sans commentaire: ça marche pour moi dans VS2008.