Admettons qu'un certain projet utilise beaucoup de mod�les (alias templates) et mette beaucoup de temps � compiler en partie � cause de cela.
Le projet contient des ent�tes qui contiennent � chaque fois la d�finition des mod�les qu'il d�clare.
Pour r�duire le temps de compilation, quand une unit� de compilation utilise une instance de mod�le de fonction (par exemple foo<int>()) ou une fonction d'une instance d'un mod�le de classe (par exemple Toto<int>::bar()) :
-On aimerait �viter d'instancier la d�finition de foo<int>() ou de Toto<int>::bar() (ce que permet extern template) si cette derni�re est d�j� instanci�e dans une autre unit� de compilation.
-Alors, on aimerait �viter que cette unit� de compilation inclut des choses qui ne servent qu'� compiler foo<int>() ou Toto<int>::bar().
Je propose la proc�dure suivante :
�tape 1 : Tripler des ent�tes
L'ent�te "NomFichier.h" devient les 3 ent�tes suivants :
- NomFichier_light.h
Permet de d�clarer un ou plusieurs mod�les, sans forc�ment les d�finir. N�anmoins, pour la m�me raison qu'une classe non template a parfois des fonctions en ligne, "NomFichier_light.h" peut avoir quelques d�finitions.- NomFichier.h
Permet d'inclure toutes les d�finitions du ou des mod�les d�clar�s directement dans "NomFichier_light.h", ainsi que les d�finitions des �ventuels autres mod�les qu'ils utilisent : "NomFichier.h" inclut parfois "AutreNomFichier.h" mais jamais "AutreNomFichier_light.h".- NomFichier_timpl.h (timpl comme template implementation)
Permet d'inclure toutes les d�finitions du ou des mod�les d�clar�s directement dans "NomFichier_light.h". N�anmoins, quand c'est possible, on �vite d'inclure les d�finitions des �ventuels autres mod�les qu'ils utilisent : "NomFichier_timpl.h" pr�f�re inclure "AutreNomFichier_light.h" � "AutreNomFichier.h".
Remarque : Pour certains projets, on pourrait supprimer "NomFichier_timpl.h" pour simplifier la proc�dure. Les ent�tes seraient alors seulement doubl�s.
�tape 2 : Isoler des instanciations dans des unit�s de compilation sp�cifiques
Par exemple, admettons qu'un fichier "Zoo.cpp" utilise le mod�le de classe Toto<int> :
- On cr�e le fichier "Toto_tinst__int.cpp" qui instancie Toto<int> (tinst comme template instantiation).
- Dans "Zoo.cpp", on remplace #include "Toto.h" par #include "Toto_light.h" et on ajoute un commentaire pour dire qu'il faut inclure au projet "Toto_tinst__int.cpp".
- "Toto_tinst__int.cpp" contient soit #include "Toto.h", soit, quand cela permet de r�duire le temps de compilation, #include "Toto_timpl.h". Dans le 2e cas, il faut ajouter un commentaire dans "Toto_tinst__int.cpp" pour signaler quel(s) autre(s) fichier(s) ".cpp" il faut inclure au projet pour que �a compile.
Exemple de r�sultat de la proc�dure :
Foo est une classe tr�s simple qui sera utilis�e plus loin.
Utils :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 ////////// // Foo.h : #ifndef INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_FOO_H #define INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_FOO_H #include <iosfwd> class Foo { }; std::ostream& operator<<(std::ostream& os, const Foo& foo); std::wostream& operator<<(std::wostream& os, const Foo& foo); #endif //////////// // Foo.cpp #include "Foo.h" #include <ostream> std::ostream& operator<<(std::ostream& os, const Foo& foo) { os << "ASCII foo\n"; return os; } std::wostream& operator<<(std::wostream& os, const Foo& foo) { os << L"wide foo\n"; return os; }
- Utils est un espace de nom qui contient une fonction normale et un mod�le de fonction print.
- Il n'y a pas de distinction entre "Utils.h" et "Utils_timpl.h".
- "Utils_tinst__charAndVectorFoo.cpp" instancie la d�finition de Utils::print<char, std::vector<Foo>>.
FooContainerPrinter :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 //////////////// // Utils_light.h #ifndef INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_UTILS_LIGHT_H #define INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_UTILS_LIGHT_H #include <iosfwd> namespace Utils { void nonTemplateFunction(); template<class CharT, class ContainerT> void print(std::basic_ostream<CharT>& os, const ContainerT& container); } #endif //////////// // Utils.cpp #include "Utils_light.h" namespace Utils { void nonTemplateFunction() { // code... } } //////////////// // Utils_timpl.h #ifndef INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_UTILS_TIMPL_H #define INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_UTILS_TIMPL_H #include "Utils_light.h" #include <ostream> namespace Utils { template<class CharT, class ContainerT> void print(std::basic_ostream<CharT>& os, const ContainerT& container) { for(const auto& element : container) os << element; } } #endif ////////// // Utils.h #include "Utils_timpl.h" //////////////////////////////////// // Utils_tinst__charAndVectorFoo.cpp #include "Utils.h" #include "Foo.h" #include <vector> template void Utils::print<char, std::vector<Foo>>( std::ostream& os, const std::vector<Foo>& container);
- FooContainerPrinter est un mod�le de classe qui utilise Foo et Utils.
- Il y a une distinction entre "FooContainerPrinter.h" et "FooContainerPrinter_timpl.h" : "FooContainerPrinter.h" inclut "Utils.h". La cons�quence est illustr�e dans les deux points suivants.
- "FooContainerPrinter_tinst__charAndVector.cpp" inclut "FooContainerPrinter_timpl.h" mais a besoin que le projet contienne "Utils_tinst__charAndVectorFoo.cpp" qui instancie la d�finition de Utils::print<char, std::vector<Foo>>.
- "FooContainerPrinter_tinst__wchar_tAndDeque.cpp" inclut "FooContainerPrinter.h" qui est plus lourd que "FooContainerPrinter_timpl.h", mais c'est n�cessaire car il faut instancier la d�finition de Utils::print<wchar_t, std::deque<Foo>> et aucun autre ".cpp" le fait.
Exemple d'utilisation :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 ////////////////////////////// // FooContainerPrinter_light.h #ifndef INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_FOO_CONTAINER_PRINTER_LIGHT_H #define INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_FOO_CONTAINER_PRINTER_LIGHT_H #include <iosfwd> #include <type_traits> class Foo; template<class CharT> class FooContainerPrinter { public: inline explicit FooContainerPrinter(std::basic_ostream<CharT>& os) : m_os(os) {} template<class ContainerT> inline void print(const ContainerT& container) const { static_assert(std::is_same<Foo, typename ContainerT::value_type>::value, "The container argument must be a container of Foo."); printImpl(container); } // Inline implicit functions: // -destructor // -copy constructor // -move constructor private: template<class ContainerT> void printImpl(const ContainerT& container) const; std::basic_ostream<CharT>& m_os; }; #endif ////////////////////////////// // FooContainerPrinter_timpl.h #ifndef INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_FOO_CONTAINER_PRINTER_TIMPL_H #define INCLUDE_FAST_TEMPLATE_COMPILATION_PROJECT_FOO_CONTAINER_PRINTER_TIMPL_H #include "FooContainerPrinter_light.h" #include "Utils_light.h" template<class CharT> template<class ContainerT> void FooContainerPrinter<CharT>::printImpl(const ContainerT& container) const { Utils::print(m_os, container); } #endif //////////////////////// // FooContainerPrinter.h #include "FooContainerPrinter_timpl.h" #include "Utils.h" /////////////////////////////////////////////// // FooContainerPrinter_tinst__charAndVector.cpp // Remark: "Utils_tinst__charAndVectorFoo.cpp" must be added to the project. #include "FooContainerPrinter_timpl.h" #include "Foo.h" #include <vector> template class FooContainerPrinter<char>; template void FooContainerPrinter<char>::printImpl<std::vector<Foo>>(const std::vector<Foo>& container) const; template void FooContainerPrinter<char>::print <std::vector<Foo>>(const std::vector<Foo>& container) const; ///////////////////////////////////////////////// // FooContainerPrinter_tinst__wchar_tAndDeque.cpp #include "FooContainerPrinter.h" #include "Foo.h" #include <deque> template class FooContainerPrinter<wchar_t>; template void FooContainerPrinter<wchar_t>::printImpl<std::deque<Foo>>(const std::deque<Foo>& container) const; template void FooContainerPrinter<wchar_t>::print <std::deque<Foo>>(const std::deque<Foo>& container) const;
On garde la r�trocompatibilit� avec la m�thode consistant � inclure "FooContainerPrinter.h" sans instancier les d�finitions de FooContainerPrinter::print dans d'autres ".cpp" :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 ///////////////////////// // WithLightInclusion.cpp // Remark: "FooContainerPrinter_tinst__charAndVector.cpp" must be added to the project. // Remark: "FooContainerPrinter_tinst__wchar_tAndDeque.cpp" must be added to the project. #include "Foo.h" #include "FooContainerPrinter_light.h" #include <deque> #include <iostream> #include <vector> namespace WithLightInclusion { void doSomething() { std::cout << "WithLightInclusion::doSomething() :\n"; std::vector<Foo> vectorFoo(2); FooContainerPrinter<char> fooChar(std::cout); fooChar.print(vectorFoo); std::deque<Foo> dequeFoo(3); FooContainerPrinter<wchar_t> fooWchar_t(std::wcout); fooWchar_t.print(dequeFoo); } };
Remarque � propos des extern template
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 ///////////////////////// // WithUsualInclusion.cpp #include "Foo.h" #include "FooContainerPrinter.h" #include <array> #include <iostream> #include <list> namespace WithUsualInclusion { void doSomething() { std::cout << "WithUsualInclusion::doSomething() :\n"; std::list<Foo> listFoo; listFoo.push_back(Foo()); listFoo.push_back(Foo()); FooContainerPrinter<char> fooChar(std::cout); fooChar.print(listFoo); std::array<Foo, 3> arrayFoo; FooContainerPrinter<wchar_t> fooWchar_t(std::wcout); fooWchar_t.print(arrayFoo); } };
Au lieu de tripler les ent�tes, on aurait pu utiliser les extern template pour �viter d'instancier plusieurs fois une m�me d�finition avec les m�mes types, mais :
- Ce ne sont pas les extern template qui permettront d'inclure des ent�tes n'ayant que le strict minimum.
- Les extern template emp�chent l'inlining, contrairement � la proc�dure que je propose dans laquelle on peut laisser quelques d�finitions dans "NomFichier_light.h".
- Les ".cpp" utilisant les extern template auraient �t� plus lourds � �crire et � maintenir : voir exemple ci-dessous.
Voici ce que �a donne avec mon exemple :
Bilan
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 ///////////////////////// // WithExternTemplate.cpp // Remark: "FooContainerPrinter_tinst__charAndVector.cpp" must be added to the project. // Remark: "FooContainerPrinter_tinst__wchar_tAndDeque.cpp" must be added to the project. #include "Foo.h" #include "FooContainerPrinter.h" #include <deque> #include <iostream> #include <vector> extern template void Utils::print<char, std::vector<Foo>>( std::ostream& os, const std::vector<Foo>& container); extern template class FooContainerPrinter<char>; extern template void FooContainerPrinter<char>::printImpl<std::vector<Foo>>(const std::vector<Foo>& container) const; extern template void FooContainerPrinter<char>::print <std::vector<Foo>>(const std::vector<Foo>& container) const; extern template class FooContainerPrinter<wchar_t>; extern template void FooContainerPrinter<wchar_t>::printImpl<std::deque<Foo>>(const std::deque<Foo>& container) const; extern template void FooContainerPrinter<wchar_t>::print <std::deque<Foo>>(const std::deque<Foo>& container) const; namespace WithExternTemplate { void doSomething() { std::cout << "WithExternTemplate::doSomething() :\n"; std::vector<Foo> vectorFoo(2); FooContainerPrinter<char> fooChar(std::cout); fooChar.print(vectorFoo); std::deque<Foo> dequeFoo(3); FooContainerPrinter<wchar_t> fooWchar_t(std::wcout); fooWchar_t.print(dequeFoo); } };
La proc�dure que je propose devrait r�duire le temps de compilation pour les mod�les qui ont des d�finitions lourdes qui sont instanci�es avec les m�mes types dans plein d'unit�s de compilation du m�me projet.
Mais cette proc�dure me semble assez lourde � mettre en place et rendra le code plus difficile � comprendre (donc plus difficile � maintenir) pour les d�veloppeurs ne connaissant pas cette proc�dure.
Donc, � mon avis, ce serait � utiliser en dernier recours, apr�s les autres m�thodes pour r�duire le temps de compilation (remplacer des #include par des d�clarations en avance, utiliser pimpl...).
Auriez-vous des am�liorations � sugg�rer pour cette proc�dure ?
Partager