58 votes

Débogage des instanciations de modèles

Quand on fait de la métaprogrammation à l'aide de modèles C++, est-il une méthode qui peut être utilisée, comme une sorte de débogueur, à l'étape à travers la façon dont les modèles sont instanciés et respecté? Il semble dès maintenant, lors de la création d'un réseau compliqué de modèles, il n'y a vraiment pas une très bonne façon de leur débogage autre que de regarder les messages d'erreur du compilateur pour voir comment les modèles sont instanciés (s'il y a des erreurs de compilation), et la tentative de travailler à rebours à partir des messages d'erreur si quelque chose d'inattendu est généré. Je ne suis pas vraiment sûr si ce que je suis à la recherche de même qu'il existe, comme il devrait être quelque chose qui se fait au moment de la compilation, mais au fond, ce serait une méthode, un peu comme entrer dans code et l'examen de la trame de pile en gdb lors de l'exécution, où le compilateur pourrait être arrêté et de l'environnement a examiné pour la séquence par laquelle un modèle ou un ensemble de modèles imbriqués est instanciée.

Par exemple, disons que j'ai créé un code simple comme suit:

template<typename T, typename R = void>
struct int_return_type {};

template<typename R>
struct int_return_type<int, R>
{
    typedef R type;
};

template<typename T, typename R = void>
struct float_return_type {};

template<typename R>
struct float_return_type<float, R> 
{
    typedef R type;
};

template<typename T>
typename int_return_type<T>::type test()
{
    cout << "T type is int" << endl;
}

template<typename T>
typename float_return_type<T>::type test()
{
    cout << "T type is float" << endl;
}

int main()
{
    test<int>();
    test<float>();
    return 0;
}

Je sais que c'est relativement facile de code à suivre, mais les modèles peuvent être tout à fait un peu plus compliqué, surtout quand on fait de la métaprogrammation, la récursivité, etc. Je comprends que le compilateur va émettre des messages d'erreur qui peuvent être utilisées pour déduire la façon dont les modèles sont instanciés, mais je me demande aussi ce qui peut être fait lorsque le modèle de code est correct dans un sens syntaxique, mais le moteur d'exécution résultats sont toujours incorrectes. Il serait bien par exemple de disposer d'une méthode pour arrêter le compilateur et de voir ce qu' test, ainsi qu' int_return_type et float_return_type, a été instanciée avec, ou ce que les instanciations ont été un échec.

Sont les seules options disponibles dès maintenant pour le débogage des modèles à ce niveau de granularité 1) les messages d'erreur du compilateur lorsque le code est incorrect, et 2) une combinaison des désassembleurs et débogueurs pour voir ce que instancié code a été généré si les résultats sont incorrects? Ou il y en a quelques autres utilitaires qui l'aidera à "regarder" la façon dont les modèles sont instanciés, et de voir/inspecter ce code est généré par le compilateur d'enquêter et de débogage modèle erreurs?

36voto

Tom Kerr Points 5716

Ce sont assez de base, mais ils ont travaillé pour moi dans la plupart des cas. Je suis curieux de voir ce que les autres ont à dire trop.

Toutes mes excuses pour les exemples inventées.

L'utilisation des bacs à sable

En commençant par de petits bacs à sable pour tester le code du modèle dès qu'il commence à se comporter bizarre ou que vous faites quelque chose de compliqué. Je suis assez à l'aise avec les modèles et je n'ai toujours cette presque immédiatement. Simplement, il découvre plus rapidement les erreurs. Vous l'avez fait pour nous ici, alors je suppose que c'est discutable.

Spécifier les types temporaire

Temporaires peuvent obscurcir où vos intentions ne sont pas remplies. J'ai vu beaucoup de code qui fait quelque chose comme ci-dessous.

template<typename T>
  T calc(const T &val) {
    return some_other_calc(val) / 100.0;
  }

Dire au compilateur quel type que vous attendez échouera plus rapides et potentiellement vous donnera un message à traiter.

template<typename T>
  T calc(const T &val) {
    T val_ = some_other_calc(val);
    return val_ / 100.0;
  }

Utilisation typeid

À l'aide de typeid(T).name() d'imprimer les noms de modèle dans des instructions de débogage. Cela vous donnera une chaîne de caractères que vous pouvez utiliser pour voir comment le compilateur a décidé de réaliser le type.

template<typename T>
  typename void test() {
    std::cout << "testing type " << typeid(T).name() << std::endl;
    // ...
  }

Éviter les implémentations par défaut

Écrire des modèles de telle manière qu'ils ne ont des implémentations par défaut.

template<typename T, bool is_integral = boost::is_numeric<T>::value >
  struct my_traits;

template<typename T>
  struct my_traits<T, true> {
    typedef uint32_t cast_type;
  };

template<typename T>
  void print_whole_number(T &val) {
    std::cout << static_cast<my_traits<T>::cast_type>(val) << std::endl;
  }

Ceci impose aux utilisateurs de print_whole_number ont leur propre my_traits de la spécialisation. Ils obtiendrez une erreur de compilation au lieu de la moitié de travailler parce que vous ne pouvait pas fournir une bonne mise en œuvre pour tous les types. L'erreur de compilateur ne sera pas immédiatement utile si elle est utilisée dans un disparates partie d'une base de code, il faut l'admettre.

5voto

Kornel Kisielewicz Points 26556

J'aime utiliser l'excellent compilateur Web Comeau pour le débogage. Il peut remarquer des erreurs en termes de compilance standard là où les autres compilateurs ne peuvent pas ...

Comeau a le gros avantage de donner beaucoup plus de messages d'erreur lisibles que GCC ou MSVC.

En outre, n'oubliez pas d'utiliser static_assert partout où cela est possible - même si vous êtes certain que la réponse est vraie.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X