web-dev-qa-db-fra.com

Utilisation de `extern template` avec une bibliothèque d'en-tête tiers uniquement

J'utilise la bibliothèque glm , qui est une collection d'utilitaires mathématiques uniquement en-tête destinée aux graphiques 3D. En utilisant -ftime-trace sur Clang et ClangBuildAnalyzer , j'ai remarqué que beaucoup de temps est passé à instancier les types glm:

**** Templates that took longest to instantiate:
 16872 ms: glm::vec<4, signed char, glm::packed_highp> (78 times, avg 216 ms)
 15675 ms: glm::vec<4, unsigned char, glm::packed_highp> (78 times, avg 200 ms)
 15578 ms: glm::vec<4, float, glm::packed_highp> (78 times, avg 199 ms)

...

J'ai donc décidé de créer une paire d'en-tête/source de wrapper pour glm, et d'utiliser extern template pour éviter les instanciations inutiles:

// glmwrapper.h

#pragma once

#include <glm.hpp>

extern template struct glm::vec<4, signed char, glm::packed_highp>;
extern template struct glm::vec<4, unsigned char, glm::packed_highp>;
extern template struct glm::vec<4, float, glm::packed_highp>;
// glmwrapper.cpp

template struct glm::vec<4, signed char, glm::packed_highp>;
template struct glm::vec<4, unsigned char, glm::packed_highp>;
template struct glm::vec<4, float, glm::packed_highp>;

Maintenant, dans mon projet, au lieu d'inclure <glm.hpp>, J'inclus "glmwrapper.h" au lieu. Malheureusement, cela n'a rien changé. En utilisant -ftime-trace et ClangBuildAnalyzer rapporte à nouveau le même nombre d'instanciations. Il n'y a pas non plus de différence de temps de compilation mesurable.

I suspect que c'est parce que #include <glm.hpp> finit par inclure la définition du modèle, et à ce moment-là, le extern template les déclarations sont simplement redondantes.

Existe-t-il un moyen d'obtenir ce que je veux sans modifier la bibliothèque glm?


En pseudocode, je veux quelque chose comme ça:

// glmwrapper.h (psuedocode)

#pragma once

#include <glm.hpp>

// Make definition of the templates unavailable:
undefine template struct glm::vec<4, signed char, glm::packed_highp>;
undefine template struct glm::vec<4, unsigned char, glm::packed_highp>;
undefine template struct glm::vec<4, float, glm::packed_highp>;

// Make declaration of the templates available:
extern template struct glm::vec<4, signed char, glm::packed_highp>;
extern template struct glm::vec<4, unsigned char, glm::packed_highp>;
extern template struct glm::vec<4, float, glm::packed_highp>;
// glmwrapper.cpp (psuedocode)

// Define templates only in the `.cpp`, not in the header:
template struct glm::vec<4, signed char, glm::packed_highp>;
template struct glm::vec<4, unsigned char, glm::packed_highp>;
template struct glm::vec<4, float, glm::packed_highp>;
9
Vittorio Romeo

Malheureusement, il n’ya aucun moyen d’éviter ces instanciations. Une déclaration d'instanciation explicite d'un modèle class n'empêche pas (implicite) l'instanciation de ce modèle; il empêche simplement d'instancier ses fonctions membres non-inline, non-template (ce qui n'est souvent aucune d'entre elles!) parce qu'une autre unité de traduction fournira les symboles de fonction et le code objet réels.

Ce n'est pas que voir la définition du modèle provoque une instanciation (quelle spécialisation serait instanciée?). La raison en est que le code qui exige que la classe soit complète doit toujours connaître sa disposition et les déclarations des fonctions membres (pour la résolution de surcharge), et en général, il y a aucun moyen de connaître ceux qui n'ont pas instancié la classe:

template<class T> struct A : T::B {
  typename std::conditional<sizeof(T)<8,long,short>::type first;
  typename T::X second;
  A() noexcept(T::y)=default;  // perhaps deleted
  using T::B::foo;
  void foo(T);
  // and so on…
};

void f() {A<C> a; a.foo(a.first);}  // …maybe?

Cette "transparence" s'étend aussi à plusieurs autres types d'entités modèles: si compilation nécessite la définition d'un modèle, les symboles générés pour le linker ne sont pas pertinents.

La bonne nouvelle est que les modules de C++ 20 devraient aider dans des situations comme celle-ci: une instanciation explicite definition dans un L'interface du module entraînera une implémentation typique pour mettre en cache la définition de classe instanciée avec le reste des données d'interface du module, évitant à la fois l'analyse et l'instanciation lors de l'importation des unités de traduction. Les modules suppriment également les inline implicites sur les membres de la classe et les amis définis dans la classe (ce qui n'a pas signifié grand chose depuis longtemps de toute façon), augmentant le nombre (ou, autrement dit, la commodité) de fonctions pour lesquelles les déclarations d'instanciation explicites empêchent l'instanciation implicite.

2
Davis Herring