en utilisant un modèle externe (C ++ 11)

116

Figure 1: modèles de fonction

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){
   //...
}    
//explicit instantation
template void f<T>();

Main.cpp

#include "TemplHeader.h"
extern template void f<T>(); //is this correct?
int main() {
    f<char>();
    return 0;
}

Est-ce la bonne façon d'utiliser extern template, ou dois-je utiliser ce mot clé uniquement pour les modèles de classe comme dans la figure 2?

Figure 2: modèles de classe

TemplHeader.h

template<typename T>
class foo {
    T f();
};

TemplCpp.cpp

template<typename T>
void foo<T>::f() {
    //...
}
//explicit instantation
template class foo<int>;

Main.cpp

#include "TemplHeader.h"
extern template class foo<int>();
int main() {
    foo<int> test;
    return 0;
}

Je sais qu'il est bon de mettre tout cela dans un seul fichier d'en-tête, mais si nous instancions des modèles avec les mêmes paramètres dans plusieurs fichiers, nous avons alors plusieurs définitions identiques et le compilateur les supprimera toutes (sauf une) pour éviter les erreurs. Comment utiliser extern template? Pouvons-nous l'utiliser uniquement pour les classes, ou pouvons-nous l'utiliser aussi pour les fonctions?

De plus, les figures 1 et 2 peuvent être développées en une solution dans laquelle les modèles sont dans un seul fichier d'en-tête. Dans ce cas, nous devons utiliser le extern templatemot - clé pour éviter plusieurs instanciations identiques. Est-ce uniquement pour les classes ou les fonctions?

codekiddy
la source
3
Ce n'est pas du tout l'utilisation correcte des modèles externes ... cela ne compile même pas
Dani
Pourriez-vous prendre le temps de formuler la (une) question plus clairement? Pourquoi affichez-vous le code? Je ne vois pas de question à ce sujet. En outre, cela extern template class foo<int>();semble être une erreur.
sehe
@Dani> il compile très bien sur mon studio visuel 2010 sauf le message d'avertissement: Avertissement 1 avertissement C4231: extension non standard utilisée: 'extern' avant l'instanciation explicite du modèle
codekiddy
2
@sehe question est très simple: comment et quand utiliser un mot-clé de modèle externe? (le modèle externe est C ++ 0x new future btw) vous avez dit "De plus, la classe de modèle extern foo <int> (); semble être une erreur." non ce n'est pas le cas, j'ai un nouveau livre C ++ et c'est un exemple de mon livre.
codekiddy
1
@codekiddy: alors visual studio est vraiment stupide .. dans le second, le prototype ne correspond pas à l'implémentation, et même si je corrige cela, il dit 'attendu unqualified-id' près ()de la ligne externe. votre livre et votre studio visuel sont faux, essayez d'utiliser un compilateur plus conforme aux normes comme g ++ ou clang et vous verrez le problème.
Dani

Réponses:

181

Vous ne devez utiliser extern templatepour forcer le compilateur à ne pas instancier un modèle lorsque vous savez qu'il sera instancié ailleurs. Il est utilisé pour réduire le temps de compilation et la taille du fichier objet.

Par exemple:

// header.h

template<typename T>
void ReallyBigFunction()
{
    // Body
}

// source1.cpp

#include "header.h"
void something1()
{
    ReallyBigFunction<int>();
}

// source2.cpp

#include "header.h"
void something2()
{
    ReallyBigFunction<int>();
}

Cela entraînera les fichiers objets suivants:

source1.o
    void something1()
    void ReallyBigFunction<int>()    // Compiled first time

source2.o
    void something2()
    void ReallyBigFunction<int>()    // Compiled second time

Si les deux fichiers sont liés ensemble, l'un void ReallyBigFunction<int>()sera ignoré, ce qui entraînera une perte de temps de compilation et de taille de fichier objet.

Pour ne pas perdre de temps de compilation et de taille de fichier objet, il existe un externmot clé qui empêche le compilateur de compiler une fonction modèle. Vous devriez l'utiliser si et seulement si vous savez qu'il est utilisé dans le même binaire ailleurs.

Changer source2.cpppour:

// source2.cpp

#include "header.h"
extern template void ReallyBigFunction<int>();
void something2()
{
    ReallyBigFunction<int>();
}

Donnera les fichiers objets suivants:

source1.o
    void something1()
    void ReallyBigFunction<int>() // compiled just one time

source2.o
    void something2()
    // No ReallyBigFunction<int> here because of the extern

Lorsque les deux seront liés ensemble, le deuxième fichier objet utilisera simplement le symbole du premier fichier objet. Pas besoin de rejeter et pas de temps de compilation perdu ni de taille de fichier objet.

Cela ne doit être utilisé que dans un projet, comme lorsque vous utilisez un modèle vector<int>plusieurs fois, vous devez l'utiliser externdans tous les fichiers source sauf un.

Cela s'applique également aux classes et aux fonctions comme des fonctions membres uniques et même de modèle.

Dani
la source
2
@codekiddy: Je n'ai aucune idée de ce que Visual Studio entend par là. Vous devriez vraiment utiliser un compilateur plus conforme si vous souhaitez que la plupart du code C ++ 11 fonctionne correctement.
Dani
4
@Dani: la meilleure explication des modèles externes que j'ai lus jusqu'à présent!
Pietro
90
"si vous savez qu'il est utilisé dans le même binaire ailleurs.". Cela n'est ni suffisant ni requis. Votre code est "mal formé, aucun diagnostic requis". Vous n'êtes pas autorisé à compter sur une instanciation implicite d'un autre TU (le compilateur est autorisé à l'optimiser, un peu comme une fonction en ligne). Une instanciation explicite doit être fournie dans une autre TU.
Johannes Schaub - litb
32
Je tiens à souligner que cette réponse est probablement erronée et que cela m'a mordu. Heureusement, le commentaire de Johannes a eu un certain nombre de votes et j'y ai prêté plus d'attention cette fois-ci. Je ne peux que supposer que la grande majorité des votants sur cette question n'ont pas réellement implémenté ces types de modèles dans plusieurs unités de compilation (comme je l'ai fait aujourd'hui) ... Au moins pour clang, le seul moyen infaillible est de mettre ces définitions de modèles dans votre en-tête! Être averti!
Steven Lu
6
@ JohannesSchaub-litb, pourriez-vous élaborer un peu plus ou peut-être fournir une meilleure réponse? Je ne sais pas si j'ai bien compris vos objections.
andreee
48

Wikipedia a la meilleure description

En C ++ 03, le compilateur doit instancier un modèle chaque fois qu'un modèle entièrement spécifié est rencontré dans une unité de traduction. Si le modèle est instancié avec les mêmes types dans de nombreuses unités de traduction, cela peut augmenter considérablement les temps de compilation. Il n'y a aucun moyen d'empêcher cela dans C ++ 03, donc C ++ 11 a introduit des déclarations de modèles externes, analogues aux déclarations de données externes.

C ++ 03 a cette syntaxe pour obliger le compilateur à instancier un modèle:

  template class std::vector<MyClass>;

C ++ 11 fournit désormais cette syntaxe:

  extern template class std::vector<MyClass>;

qui indique au compilateur de ne pas instancier le modèle dans cette unité de traduction.

L'avertissement: nonstandard extension used...

Microsoft VC ++ avait déjà une version non standard de cette fonctionnalité depuis quelques années (en C ++ 03). Le compilateur avertit à ce sujet pour éviter les problèmes de portabilité avec le code qui devait également être compilé sur différents compilateurs.

Regardez l'exemple dans la page liée pour voir qu'il fonctionne à peu près de la même manière. Vous pouvez vous attendre à ce que le message disparaisse avec les futures versions de MSVC, sauf bien sûr lors de l'utilisation d' autres extensions de compilateur non standard en même temps.

sehe
la source
tnx pour votre réponse sehe, donc ce que signifie cette acctualy est que le futur "extern template" fonctionne pleinement pour VS 2010 et nous pouvons simplement ignorer l'avertissement? (en utilisant pragma pour ignorer le message par exemple) et assurez-vous que le modèle n'est pas instancié plus que le temps dans VSC ++. compilateur. Merci.
codekiddy
4
"... qui dit au compilateur de ne pas instancier le modèle dans cette unité de traduction ." Je ne pense pas que ce soit vrai. Toute méthode définie dans la définition de classe compte comme en ligne, donc si l'implémentation STL utilise des méthodes en ligne pour std::vector(presque sûr que toutes le font), externn'a aucun effet.
Andreas Haferburg
Oui, cette réponse est trompeuse. MSFT doc: "Le mot-clé extern dans la spécialisation ne s'applique qu'aux fonctions membres définies en dehors du corps de la classe. Les fonctions définies dans la déclaration de classe sont considérées comme des fonctions en ligne et sont toujours instanciées." Toutes les classes STL dans VS (la dernière vérification remonte à 2017) n'ont malheureusement que des méthodes en ligne.
0kcats
Cela vaut pour toutes les déclarations inline quel que soit l'endroit où ils se présentent, toujours @ 0kcats
sehe
@sehe La référence à Wiki avec l'exemple de std :: vector et une référence à MSVC dans la même réponse laisse penser qu'il pourrait y avoir un avantage à utiliser extern std :: vector dans MSVC, alors qu'il n'y en a pas pour l'instant. Je ne sais pas si c'est une exigence de la norme, peut-être que d'autres compilateurs ont le même problème.
0kcats
7

extern template n'est nécessaire que si la déclaration du modèle est complète

Cela a été évoqué dans d'autres réponses, mais je ne pense pas qu'on y ait suffisamment insisté.

Cela signifie que dans les exemples OP, le extern templaten'a aucun effet car les définitions de modèle sur les en-têtes étaient incomplètes:

  • void f();: juste déclaration, pas de corps
  • class foo: déclare la méthode f()mais n'a pas de définition

Je recommanderais donc de supprimer simplement la extern templatedéfinition dans ce cas particulier: vous ne devez les ajouter que si les classes sont complètement définies.

Par exemple:

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){}

// Explicit instantiation for char.
template void f<char>();

Main.cpp

#include "TemplHeader.h"

// Commented out from OP code, has no effect.
// extern template void f<T>(); //is this correct?

int main() {
    f<char>();
    return 0;
}

compilez et visualisez des symboles avec nm:

g++ -std=c++11 -Wall -Wextra -pedantic -c -o TemplCpp.o TemplCpp.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -c -o Main.o Main.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -o Main.out Main.o TemplCpp.o
echo TemplCpp.o
nm -C TemplCpp.o | grep f
echo Main.o
nm -C Main.o | grep f

production:

TemplCpp.o
0000000000000000 W void f<char>()
Main.o
                 U void f<char>()

et puis à partir de man nmnous voyons que cela Usignifie indéfini, donc la définition n'est restée que TemplCppcomme souhaité.

Tout cela se résume au compromis des déclarations d'en-tête complètes:

  • avantages:
    • permet au code externe d'utiliser notre modèle avec de nouveaux types
    • nous avons la possibilité de ne pas ajouter d'instanciations explicites si nous sommes d'accord avec le gonflement d'objet
  • inconvénients:
    • lors du développement de cette classe, les changements d'implémentation des en-têtes conduiront les systèmes de construction intelligents à reconstruire tous les inclusions, ce qui pourrait être de nombreux fichiers
    • si nous voulons éviter le gonflement du fichier objet, nous devons non seulement faire des instanciations explicites (comme pour les déclarations d'en-tête incomplètes), mais également ajouter extern templatechaque inclusion, ce que les programmeurs oublieront probablement de faire

D'autres exemples de ceux-ci sont présentés à: Instanciation de modèle explicite - quand est-elle utilisée?

Étant donné que le temps de compilation est si critique dans les grands projets, je recommande vivement les déclarations de modèle incomplètes, à moins que des parties externes n'aient absolument besoin de réutiliser votre code avec leurs propres classes personnalisées complexes.

Et dans ce cas, j'essaierais d'abord d'utiliser le polymorphisme pour éviter le problème de temps de construction, et n'utiliserais des modèles que si des gains de performances notables peuvent être réalisés.

Testé dans Ubuntu 18.04.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
4

Le problème connu avec les modèles est le gonflement du code, qui est la conséquence de la génération de la définition de classe dans chaque module qui invoque la spécialisation du modèle de classe. Pour éviter cela, en commençant par C ++ 0x, on pourrait utiliser le mot-clé extern devant la spécialisation du modèle de classe

#include <MyClass>
extern template class CMyClass<int>;

L'instanciation explicite de la classe de modèle ne doit se produire que dans une seule unité de traduction, de préférence celle avec la définition de modèle (MyClass.cpp)

template class CMyClass<int>;
template class CMyClass<float>;
Damirlj
la source
0

Si vous avez déjà utilisé extern pour les fonctions, la même philosophie est suivie pour les modèles. sinon, passer par extern pour des fonctions simples peut aider. En outre, vous souhaiterez peut-être placer le (s) externe (s) dans le fichier d'en-tête et inclure l'en-tête lorsque vous en avez besoin.

qqqqq
la source