126 votes

Pourquoi est-ce que je reçois des erreurs de type "symbole externe non résolu" lorsque j'utilise des modèles ?

Lorsque j'écris du code C++ pour une classe en utilisant des modèles et que je répartis le code entre un fichier source (CPP) et un fichier d'en-tête (H), j'obtiens un grand nombre d'erreurs "unresolved external symbol" au moment de lier l'exécutable final, bien que le fichier objet soit correctement construit et inclus dans la liaison. Que se passe-t-il et comment puis-je y remédier ?

0 votes

148voto

dlanod Points 2597

Les classes et les fonctions modélisées ne sont pas instanciées tant qu'elles ne sont pas utilisées, généralement dans un fichier .cpp distinct (par exemple, la source du programme). Lorsque le modèle est utilisé, le compilateur a besoin du code complet de cette fonction pour pouvoir construire la fonction correcte avec le type approprié. Cependant, dans ce cas, le code de cette fonction est détaillé dans le fichier source du modèle et n'est donc pas disponible.

Le résultat de tout cela est que le compilateur suppose qu'il est défini ailleurs et insère seulement l'appel à la fonction modèle. Lorsqu'il s'agit de compiler le fichier source du modèle, le type de modèle spécifique qui est utilisé dans la source du programme n'est pas utilisé, de sorte qu'il ne génère toujours pas le code requis pour la fonction. Il en résulte un symbole externe non résolu.

Les solutions disponibles pour cela sont les suivantes :

  1. inclure la définition complète de la la fonction membre dans le fichier d'en-tête du modèle et ne pas avoir un fichier source pour le modèle,
  2. définir toutes les fonctions membres dans le fichier source du modèle comme "en ligne" (Mise à jour : [cela ne fonctionne pas sur Visual Studio 2017+]), ou
  3. définir les fonctions membres dans la source du modèle avec le mot-clé "export". Malheureusement, ceci n'est pas supporté par beaucoup de compilateurs. (Mise à jour : Ceci a été supprimé de la norme à partir de C++11. .)

Les points 1 et 2 résolvent le problème en donnant au compilateur l'accès au code complet de la fonction modèle lorsqu'il tente de construire la fonction typée dans la source du programme.

4 votes

En (3) vous avez une erreur de frappe. Vous vouliez probablement dire "mot-clé" et non "clavier". Je ne vois pas en quoi le fait de définir les fonctions comme étant "en ligne" est utile. Vous devez les placer dans l'en-tête ou instancier explicitement les modèles en utilisant les types dont vous avez besoin.

11 votes

Vous devriez éventuellement reformuler le point (2). je n'ai aucune idée de ce que vous entendez par là

1 votes

Le mot-clé "export" fournit également la définition complète. Elle peut être sous une forme légèrement codée, comme un arbre d'analyse du compilateur, mais elle n'est pas très bien cachée. Bien sûr, je suppose que le code machine ne cache pas très bien la source non plus.

24voto

shoosh Points 34322

Une autre option consiste à placer le code dans le fichier cpp et à ajouter dans ce même fichier cpp des instanciations explicites du modèle avec les types que vous pensez utiliser. C'est utile si vous savez que vous ne l'utiliserez que pour quelques types que vous connaissez à l'avance.

8 votes

Donc, en gros, vous dites f**** ***u à la modularité, à la réutilisation, à la responsabilité unique et à la séparation des préoccupations... et à tout l'intérêt de la programmation générique, qui consiste à disposer de classes génériques pouvant être utilisées avec tous les types que vous souhaitez. sans que la classe modèle le sache au préalable à quoi va-t-il servir ?

11 votes

@jbx Je dis que pour des choses comme basic_string<T> vous ne l'utiliserez jamais qu'avec char ou wchar_t donc si mettre toute l'implémentation dans l'en-tête est un souci, l'instancier dans le cpp est une option. C'est vous qui commandez le code, et non l'inverse.

0 votes

Cela va à l'encontre de l'objectif des modèles, à mon avis. Votre exemple n'est qu'une exception (qui aurait sans doute dû être écrite en utilisant la surcharge si elle ne concerne que deux types, mais c'est un autre argument). La programmation par modèles est censée créer quelque chose qui fonctionne indépendamment des types avec lesquels il interagit, sans savoir à l'avance. Prédire quels seront les types va à l'encontre de son objectif. C'est juste une mauvaise pratique qui "résout" le problème, mais ce n'est pas parce que vous pouvez le faire que vous devez le faire.

-3voto

totem_motorist Points 25

Pour chaque fichier qui inclut le fichier .h, vous devez insérer les deux lignes :

#include "MyfileWithTemplatesDeclaration.h"
#include "MyfileWithTemplatesDefinition.cpp"

échantillon

#include "list.h"
    #include "list.cpp" //<---for to fix bug link err 2019

    int main(int argc, _TCHAR* argv[])
    {
        list<int> my_list;
        my_list.add_end(3);
    .
    .
    } 

aussi, n'oubliez pas de placer votre classe de déclaration parmi les constantes centinelles

#ifndef LIST_H
#define LIST_H
#include <iostream>
.
.
template <class T>
class list
{
private:
    int m_size,
        m_count_nodes;
    T m_line;
    node<T> *m_head;
public:
    list(void);
    ~list(void);
    void add_end(T);
    void print();
};
#endif

13 votes

Je ne pense pas que ce soit une bonne idée. Inclure des fichiers .cpp envoie le mauvais message. Si vous voulez que l'utilisateur inclue les deux fichiers, nommez-les code.h et code_impl.h ou similaire.

3 votes

Je suis d'accord. Il y a peu de raisons d'inclure un fichier .cpp dans votre source, et selon les paramètres de votre projet, cela pourrait même donner au compilateur un mal de tête séparé.

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