Comment réduire le temps de compilation avec les modèles C++

je suis en train de changer une partie de mon application C++ pour passer d'un vieux tableau de type C à une classe de conteneur c++. Voir cette question pour plus de détails. Alors que la solution fonctionne très bien, chaque changement mineur que je fais au code templated provoque une très grande quantité de recompilation pour avoir lieu, et donc ralentit drastiquement le temps de construction. Y a-t-il un moyen de sortir le code modèle de l'en-tête et de le remettre dans un fichier cpp, de sorte que la mise en œuvre mineure les changements ne causent pas de reconstructions majeures?

30
demandé sur Community 2010-05-13 18:09:18

5 réponses

je pense que les règles générales s'appliquent. Essayez de réduire le couplage entre les parties du code. Briser trop grand modèle-têtes en plus petits groupes, des fonctions utilisées, de sorte que le tout n'aura pas à être inclus dans chaque fichier source.

aussi, essayer de mettre les en-têtes dans un état stable rapidement, peut-être les tester contre un plus petit programme de test, de sorte qu'ils n'auraient pas besoin de changer (trop) une fois intégré dans un plus grand programme.

(comme pour toute optimisation, il pourrait être moins intéressant d'optimiser pour la vitesse du compilateur lorsqu'il s'agit de gabarits, plutôt que de trouver une optimisation" algorithmique " qui réduit la charge de travail de façon drastique en premier lieu.)

16
répondu UncleBens 2010-05-13 14:59:01

plusieurs approches:

  • le export keyword peut théoriquement aider, mais il est mal supporté et a été officiellement supprimé en C++11.
  • instanciation de modèle explicite (voir ici ou ici ) est l'approche la plus simple, si vous pouvez prédire à l'avance quelles instanciations vous aurez besoin (et si vous ne vous gênez pas de maintenir cette liste).
  • modèles externes , qui sont déjà pris en charge par plusieurs compilateurs comme extensions. Je comprends que les modèles externes ne vous permettent pas nécessairement de déplacer les définitions de modèles hors du fichier d'en-tête, mais ils rendent la compilation et le lien plus rapide (en réduisant le nombre de fois où le code de modèle doit être instancié et lié).
  • Selon votre modèle de conception, vous pouvez être en mesure de déplacer la plupart de ses la complexité en un .fichier cpp. L'exemple standard est une classe de modèle de vecteur de sécurité type qui enveloppe simplement un vecteur de sécurité type de void* ; toute la complexité va dans le void* vecteur qui réside dans un .fichier cpp. Scott Meyers donne un exemple plus détaillé dans Effective C++ (point 42, "l'Usage privé de l'héritage judicieusement", dans la 2e édition).
21
répondu Josh Kelley 2015-08-29 14:33:08

tout d'abord, pour être complet, je vais couvrir la solution simple: n'utiliser le code templated que si nécessaire, et le baser sur le code non-template (avec implémentation dans son propre fichier source).

cependant, je soupçonne que le vrai problème est que vous utilisez la programmation générique que vous utiliseriez oo-programmation typique et finir avec une classe gonflée.

prenons un exemple:

// "bigArray/bigArray.hpp"

template <class T, class Allocator>
class BigArray
{
public:
  size_t size() const;

  T& operator[](size_t index);
  T const& operator[](size_t index) const;

  T& at(size_t index);
  T const& at(size_t index);

private:
  // impl
};

fait ce choc vous ? Probablement pas. Il semble assez minimaliste, après tout. La chose est, il n'est pas. Les méthodes at peuvent être prises en compte sans aucune perte de généralité:

// "bigArray/at.hpp"

template <class Container>
typename Container::reference_type at(Container& container,
                                      typename Container::size_type index)
{
  if (index >= container.size()) throw std::out_of_range();
  return container[index];
}

template <class Container>
typename Container::const_reference_type at(Container const& container,
                                            typename Container::size_type index)
{
  if (index >= container.size()) throw std::out_of_range();
  return container[index];
}

D'accord, cela change légèrement l'invocation:

// From
myArray.at(i).method();

// To
at(myArray,i).method();

cependant, grâce à la recherche de Koenig, vous pouvez les appeler non qualifiés aussi longtemps que vous les mettez dans le même espace de nom, donc c'est juste une question d'habitude.

l'exemple est inventé mais le général en est. Notez qu'en raison de sa généralité at.hpp n'a jamais eu à inclure bigArray.hpp et produira toujours le code aussi serré que si c'était une méthode membre, il est juste que nous pouvons l'invoquer sur d'autres conteneurs si nous le souhaitons.

Et maintenant, un utilisateur de BigArray n'a pas besoin d'inclure at.hpp si elle n'a pas utilise... ainsi réduire ses dépendances et ne pas être affecté si vous changez le code dans ce fichier: par exemple, modifier std::out_of_range appel à affiche le nom du fichier et le numéro de ligne, l'adresse du conteneur, sa taille et l'index auquel nous avons essayé d'accéder.

l'autre avantage (moins évident), c'est que si jamais la contrainte d'intégrité de BigArray est violée, alors at est évidemment hors de cause puisqu'il ne peut pas jouer avec les internes de la classe, réduisant ainsi le nombre de suspects.

cela est recommandé par de nombreux auteurs, comme Herb Sutters dans C++ codage Normes :

Item 44: Prefer writing unhertime nonfriend functions

et a été largement utilisé dans Boost ... Mais vous devez changer vos habitudes de codage!

alors, bien sûr, vous devez inclure seulement ce dont vous dépendez, il devrait y avoir des analyseurs de code C++ statiques qui rapportent inclus mais des fichiers d'en-tête inutilisés qui peuvent aider à comprendre cela hors.

5
répondu Matthieu M. 2014-03-10 17:50:36
  • vous pouvez obtenir un compilateur qui supporte le mot-clé export , mais cela ne durera probablement pas.

  • vous pouvez utiliser instanciation explicite , mais malheureusement, cela vous oblige à prévoir les types de gabarits que vous utiliserez à l'avance.

  • si vous pouvez prendre en compte les types templated de votre algorithme, vous pouvez le mettre dans son propre .cc fichier.

  • Je ne suggérerais pas cela, à moins que ce soit un problème majeur, mais: vous pouvez être en mesure de fournir un modèle d'interface de conteneur qui est mis en œuvre avec des appels à une mise en œuvre void* que vous êtes libre de changer à volonté.

4
répondu Stephen 2010-05-13 14:23:28

vous pouvez définir une classe de base sans gabarits et déplacer la plupart de l'implémentation là. Le tableau templated définirait alors seulement les méthodes de proxy, qui utilisent la classe de base pour tout.

2
répondu AareP 2010-05-13 14:20:55