207 votes

"Référence non définie à" constructeur de classe de modèle

Je n'ai aucune idée de pourquoi cela se passe, car je crois que j'ai tout correctement déclarées et définies.

J'ai le programme suivant, conçu avec des modèles. C'est une simple mise en œuvre d'une file d'attente, avec les fonctions de membre "ajouter", "soustraire" et "imprimer".

J'ai défini le nœud de la file d'attente à l'amende "nodo_colaypila.h":

#ifndef NODO_COLAYPILA_H
#define NODO_COLAYPILA_H

#include <iostream>

template <class T> class cola;

template <class T> class nodo_colaypila
{
        T elem;
        nodo_colaypila<T>* sig;
        friend class cola<T>;
    public:
        nodo_colaypila(T, nodo_colaypila<T>*);

};

Ensuite, la mise en œuvre de "nodo_colaypila.cpp"

#include "nodo_colaypila.h"
#include <iostream>

template <class T> nodo_colaypila<T>::nodo_colaypila(T a, nodo_colaypila<T>* siguiente = NULL)
{
    elem = a;
    sig = siguiente;//ctor
}

Par la suite, la définition et la déclaration de la file d'attente de classe de modèle et de ses fonctions:

"cola.h":

#ifndef COLA_H
#define COLA_H

#include "nodo_colaypila.h"

template <class T> class cola
{
        nodo_colaypila<T>* ult, pri;
    public:
        cola<T>();
        void anade(T&);
        T saca();
        void print() const;
        virtual ~cola();

};


#endif // COLA_H

"cola.cpp":

#include "cola.h"
#include "nodo_colaypila.h"

#include <iostream>

using namespace std;

template <class T> cola<T>::cola()
{
    pri = NULL;
    ult = NULL;//ctor
}

template <class T> void cola<T>::anade(T& valor)
{
    nodo_colaypila <T> * nuevo;

    if (ult)
    {
        nuevo = new nodo_colaypila<T> (valor);
        ult->sig = nuevo;
        ult = nuevo;
    }
    if (!pri)
    {
        pri = nuevo;
    }
}

template <class T> T cola<T>::saca()
{
    nodo_colaypila <T> * aux;
    T valor;

    aux = pri;
    if (!aux)
    {
        return 0;
    }
    pri = aux->sig;
    valor = aux->elem;
    delete aux;
    if(!pri)
    {
        ult = NULL;
    }
    return valor;
}

template <class T> cola<T>::~cola()
{
    while(pri)
    {
        saca();
    }//dtor
}

template <class T> void cola<T>::print() const
{
    nodo_colaypila <T> * aux;
    aux = pri;
    while(aux)
    {
        cout << aux->elem << endl;
        aux = aux->sig;
    }
}

Ensuite, j'ai un programme pour tester ces fonctions comme suit:

"main.cpp"

#include <iostream>
#include "cola.h"
#include "nodo_colaypila.h"

using namespace std;

int main()
{
    float a, b, c;
    string d, e, f;
    cola<float> flo;
    cola<string> str;

    a = 3.14;
    b = 2.71;
    c = 6.02;
    flo.anade(a);
    flo.anade(b);
    flo.anade(c);
    flo.print();
    cout << endl;

    d = "John";
    e = "Mark";
    f = "Matthew";
    str.anade(d);
    str.anade(e);
    str.anade(f);
    cout << endl;

    c = flo.saca();
    cout << "First In First Out Float: " << c << endl;
    cout << endl;

    f = str.saca();
    cout << "First In First Out String: " << f << endl;
    cout << endl;

    flo.print();
    cout << endl;
    str.print();

    cout << "Hello world!" << endl;
    return 0;
}

Mais quand je compiler, le compilateur renvoie des erreurs dans chaque instance de la classe de modèle:

undefined reference to `cola(float)::cola()'... (c'est en fait cola'<'float'>'::cola(), mais cela ne me permet pas de l'utiliser comme ça).

Et ainsi de suite. Au total, 17 avertissements, en comptant ceux pour les fonctions de membre d'être appelé dans le programme.

Pourquoi est-ce? Ces fonctions et les constructeurs ONT été définis. Je pensais que le compilateur peut remplacer le "T" dans le modèle avec "float", "string" ou que ce soit; c'est l'avantage de l'utilisation de modèles.

J'ai lu quelque part ici que je devrais mettre la déclaration de chaque fonction dans le fichier d'en-tête pour une raison quelconque. Est ce que le droit? Et si oui, pourquoi?

Merci à l'avance.

484voto

Aaron McDaid Points 7761

C'est une question commune que dans la programmation en C++. Il y a deux réponses valables. Il y a des avantages et des inconvénients pour les deux réponses et votre choix dépendra du contexte. La réponse commune est de tout mettre en œuvre dans le fichier d'en-tête, mais il existe une autre approche sera adaptée dans certains cas. Le choix est le vôtre.

Le code dans un template est simplement un "pattern" connu du compilateur. Le compilateur ne compile pas les constructeurs cola<float>::cola(...) et cola<string>::cola(...) jusqu'à ce qu'il est forcé de le faire. Et nous devons nous assurer que cette compilation se passe pour les constructeurs au moins une fois dans l'ensemble du processus de compilation, ou nous allons faire la "undefined reference" erreur. (Cela s'applique à d'autres méthodes d' cola<T> également.)

La compréhension du problème

Le problème est causé par le fait qu' main.cpp et cola.cpp sera calculé séparément en premier. En main.cpp, le compilateur va implicitement instancier le modèle des classes d' cola<float> et cola<string> parce que l'un de ces instanciations sont utilisés en main.cpp. La mauvaise nouvelle est que les implémentations de ces fonctions membres ne sont pas en main.cpp, ni dans aucun fichier d'en-tête inclus dans main.cpp, et donc le compilateur ne peut pas inclure les versions complètes de ces fonctions en main.o. Lors de la compilation d' cola.cpp, le compilateur ne pas compiler ces instanciations, car il n'y a pas, implicite ou explicite, les instanciations de cola<float> ou cola<string>. Rappelez-vous, lors de la compilation d' cola.cpp, le compilateur n'a aucun indice qui instanciations sera nécessaire; et nous ne pouvons pas attendre pour compiler pour chaque type afin de s'assurer que ce problème n'arrive jamais! (cola<int>, cola<char>, cola<ostream>, cola< cola<int> > ... et ainsi de suite ...)

Les deux réponses sont:

  • Indiquer au compilateur, à la fin de l' cola.cpp, qui modèle les classes seront nécessaires, en les forçant à compiler cola<float> et cola<string>.
  • Mettre la mise en œuvre des fonctions de membre dans un fichier d'en-tête qui sera incluse à chaque fois qu'un autre 'unité de traduction" (comme main.cpp) utilise le modèle de la classe.

Réponse 1: instancier Explicitement le modèle, et ses définitions de membre

À la fin de l' cola.cpp, vous devez ajouter les lignes instancier explicitement tous les modèles de formulaires, tels que

template class cola<float>;
template class cola<string>;

et vous ajoutez les deux lignes suivantes à la fin de l' nodo_colaypila.cpp:

template class nodo_colaypila<float>;
template class nodo_colaypila<std :: string>;

Cela permettra d'assurer que, lorsque le compilateur compile cola.cpp qu'il sera explicitement compiler tout le code de l' cola<float> et cola<string> les classes. De même, nodo_colaypila.cpp contient les implémentations de l' nodo_colaypila<...> les classes.

Dans cette approche, vous devez vous assurer que tous la de la mise en œuvre est placé dans un .cpp le fichier (c'est à dire une unité de traduction) et que l'explicite instantation est placé après la définition de toutes les fonctions (c'est à dire à la fin du fichier).

Réponse 2: Copier le code dans le fichier d'en-tête

La réponse commune est de déplacer tout le code de la mise en œuvre des fichiers cola.cpp et nodo_colaypila.cpp en cola.h et nodo_colaypila.h. Dans le long terme, ce qui est plus souple que cela signifie que vous pouvez utiliser des instanciations (par exemple, cola<char>), sans plus de travail. Mais on pourrait dire la même les fonctions sont compilés à de nombreuses reprises, une fois dans chaque unité de traduction. Ce n'est pas un gros problème, comme l'éditeur de liens correctement ignorer le double implémentations. Mais il pourrait ralentir la compilation un peu.

Résumé

La réponse par défaut, utilisé par la STL par exemple et en plus du code que nous allons écrire, est de mettre toutes les implémentations dans les fichiers d'en-tête. Mais dans un cadre plus privé de projet, vous aurez plus de connaissances et de contrôle de modèle de classes sera instancié. En fait, ce 'bug' peut être vu comme une fonction, il empêche les utilisateurs de votre code accidentellement à l'aide d'instanciations vous n'avez pas testé pour ou prévus ("je sais que cela fonctionne pour cola<float> et cola<string>, si vous voulez utiliser quelque chose d'autre, dites-moi en premier et peut vérifier que cela fonctionne avant de l'activer.").

Enfin, il y a trois autres de quelques fautes dans le code dans votre question:

  • Il vous manque une #endif à la fin de nodo_colaypila.h
  • au cola.h nodo_colaypila<T>* ult, pri; devrait être nodo_colaypila<T> *ult, *pri; - les deux sont des pointeurs.
  • nodo_colaypila.cpp: par défaut, Le paramètre doit être dans le fichier d'en-tête nodo_colaypila.h, pas dans ce fichier de mise en oeuvre.

16voto

Alok Save Points 115848

Vous aurez à définir les fonctions à l'intérieur de votre fichier d'en-tête.
Vous ne pouvez pas séparer la définition de modèle de fonctions dans le fichier source et les déclarations de fichier d'en-tête.

Lorsqu'un modèle est utilisé d'une manière qui déclenche son intstantation, un compilateur a besoin de voir que certains modèles de définition. C'est la raison pour modèles sont souvent définis dans le fichier d'en-tête dans lequel elles sont déclarées.

Référence:
C++03 standard, § 14.7.2.4:

La définition d'un non-fonction exportée, un modèle non exportée fonction membre template, ou un non exportée fonction membre ou à la donnée membre statique d'une classe de modèle doit être présent dans chaque unité de traduction dans lequel il est explicitement instancié.

EDIT:
Pour clarifier le débat sur les commentaires:
Techniquement, il y a trois façons de contourner ce problème de liaison:

  • Pour déplacer la définition de l' .h fichier
  • Ajouter explicite instanciations dans l' .cpp le fichier.
  • #include le .cpp le fichier de définition du modèle à l' .cpp le fichier à l'aide du modèle.

Chacun d'eux ont leurs avantages et inconvénients,

Le déplacement de la definitions de fichiers d'en-tête peut augmenter la taille du code(de jour moderne compilateurs peuvent éviter cela), mais augmentera le temps de compilation pour vous.

À l'aide de l'instanciation explicite approche est en train de revenir sur le traditionnel macro comme approche.Un autre inconvénient est qu'il est nécessaire de savoir quels types de modèle sont requis par le programme. Pour un programme simple, c'est facile, mais pour un programme compliqué cela devient difficile de déterminer à l'avance.

Tout en incluant les fichiers cpp est une source de confusion dans le même temps, les actions, les problèmes des deux approches ci-dessus.

Je trouve la première méthode, la plus simple à suivre et à mettre en œuvre et donc advocte de l'utiliser.

10voto

Liam M Points 2322

Ce lien explique où vous allez mal:

[35.12] Pourquoi je ne peux pas séparer la définition de mes modèles de la classe de sa déclaration et de le mettre à l'intérieur une .fichier cpp?

Lieu de la définition de vos constructeurs, destructeurs des méthodes et autres joyeusetés dans votre fichier d'en-tête, et qui va corriger le problème.

Cette offre une autre solution:

Comment puis-je éviter les erreurs d'édition de liens avec mon modèle?

Toutefois, cela exige de vous d'anticiper la façon dont le modèle sera utilisé comme une solution générale, est contre-intuitif. Il n'résoudre le coin le cas où vous développez un modèle à être utilisé par certains mécanisme interne, et vous voulez de la police de la façon dont il est utilisé.

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