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.
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 :
  • 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>>.

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 :
  • 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.


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;
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
/////////////////////////
// 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);
	}
};
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
/////////////////////////
// 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);
	}
};
Remarque � propos des extern template

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 :
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);
	}
};
Bilan

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 ?