"Undefined reference to" template class constructor [duplikat]

to pytanie ma już odpowiedzi tutaj : Dlaczego szablony mogą być zaimplementowane tylko w pliku nagłówkowym? (17 odpowiedzi) Zamknięte 5 lat temu.

Nie mam pojęcia, dlaczego tak się dzieje, ponieważ myślę, że mam wszystko właściwie zadeklarowane i zdefiniowane.

Mam następujący program, zaprojektowany z szablonów. Jest to prosta implementacja kolejki, z funkcjami członkowskimi "dodaj"," odstaw "i"drukuj".

Zdefiniowałem węzeł dla kolejki w fine " 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>*);

};

Następnie wdrożenie w "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
}

Następnie definicja i deklaracja klasy szablonu kolejki i jej funkcji:

"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;
    }
}

Następnie mam program do testowania tych funkcji w następujący sposób:

"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;
}

Ale kiedy buduję, kompilator wyrzuca błędy w każdej instancji klasy szablonu:

Undefined reference to ' cola (float):: cola()'... (właściwie to cola ' ':: cola (), ale to nie pozwala mi tego tak używać.)

I tak dalej. Łącznie 17 ostrzeżeń, licząc te dla funkcji Członkowskich wywołanych w programie.

Dlaczego tak jest? Te funkcje i konstruktory zostały zdefiniowane. Myślałem, że kompilator może zastąpić "T" w szablonie przez "float", "string" lub cokolwiek innego; to była zaleta używania szablonów.

Czytałem gdzieś tutaj, że powinienem umieścić deklarację każdej funkcji w plik nagłówkowy z jakiegoś powodu. Naprawdę? A jeśli tak, to dlaczego?

Z góry dzięki.
Author: Heathcliff, 2012-01-06

3 answers

Jest to często spotykane pytanie w programowaniu C++. Istnieją dwie poprawne odpowiedzi na to pytanie. Obie odpowiedzi mają swoje zalety i wady, a wybór zależy od kontekstu. Powszechną odpowiedzią jest umieszczenie całej implementacji w pliku nagłówkowym, ale w niektórych przypadkach odpowiednie będzie inne podejście. Wybór należy do ciebie.

Kod w szablonie jest jedynie 'wzorcem' znanym kompilatorowi. Kompilator nie będzie kompilował konstruktorów cola<float>::cola(...) i cola<string>::cola(...) dopóki nie będzie zmuszony do tego. I musimy upewnić się, że kompilacja ta będzie miała miejsce dla konstruktorów co najmniej raz w całym procesie kompilacji, w przeciwnym razie otrzymamy błąd 'undefined reference'. (Dotyczy to również innych metod cola<T>.)

Zrozumienie problemu

Problem jest spowodowany tym, że main.cpp i cola.cpp zostaną skompilowane osobno jako pierwsze. W main.cpp, kompilator domyślnie utworzy instancję klas szablonów cola<float> i cola<string>, ponieważ te konkretne instancje są używane w main.cpp. Zła wiadomość jest taka, że implementacje tych funkcji Członkowskich nie znajdują się w main.cpp, ani w żadnym pliku nagłówkowym zawartym w main.cpp, dlatego kompilator nie może zawierać pełnych wersji tych funkcji w main.o. Podczas kompilacji cola.cpp, kompilator nie będzie kompilował tych instancji, ponieważ nie ma ukrytych ani jawnych instancji cola<float> lub cola<string>. Pamiętaj, że podczas kompilacji cola.cpp kompilator nie ma pojęcia, które instancje będzie potrzebny; i nie możemy oczekiwać, że będzie kompilowany dla każdego typu , aby upewnić się, że ten problem nigdy się nie wydarzy! (cola<int>, cola<char>, cola<ostream>, cola< cola<int> > ... i tak dalej ...)

Dwie odpowiedzi to:

  • powiedz kompilatorowi na końcu cola.cpp, które konkretne klasy szablonów będą wymagane, zmuszając go do kompilacji cola<float> i cola<string>.
  • umieść implementację funkcji Członkowskich w pliku nagłówkowym, który będzie dołączany co raz "Jednostka tłumaczenia" (np. main.cpp) używa klasy szablonu.
Odpowiedź 1: jawnie tworzy instancję szablonu i jego definicji członów.]}

Na końcu Z cola.cpp, należy dodać wiersze bezpośrednio inicjujące wszystkie odpowiednie szablony, takie jak

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

I dodajesz na końcu nodo_colaypila.cpp następujące dwie linijki:

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

Zapewni to, że gdy kompilator kompiluje cola.cpp, to jawnie skompiluje cały kod dla cola<float> i cola<string> klasy. Podobnie, nodo_colaypila.cpp zawiera implementacje klas nodo_colaypila<...>.

W tym podejściu należy upewnić się, że cała implementacja jest umieszczona w jednym pliku .cpp (tj. w jednej jednostce translacyjnej) i że Jawna instancja jest umieszczona po definicji wszystkich funkcji (tj. na końcu pliku).

Odpowiedź 2: skopiuj kod do odpowiedniego pliku nagłówkowego

Powszechną odpowiedzią jest przeniesienie całego kodu z implementacji pliki cola.cpp i nodo_colaypila.cpp na cola.h i nodo_colaypila.h. Na dłuższą metę jest to bardziej elastyczne, ponieważ oznacza, że można używać dodatkowych instancji (np. cola<char>) BEZ więcej pracy. Ale może to oznaczać, że te same funkcje są kompilowane wiele razy, raz w każdej jednostce tłumaczenia. Nie jest to duży problem, ponieważ linker poprawnie zignoruje zduplikowane implementacje. Ale to może trochę spowolnić kompilację.

Podsumowanie

Domyślna odpowiedź, używana np. przez STL i w większości z kodu, który każdy z nas napisze, jest umieszczenie wszystkich implementacji w plikach nagłówkowych. Ale w bardziej prywatnym projekcie, będziesz miał więcej wiedzy i kontroli nad tym, które konkretne klasy szablonów zostaną utworzone. W rzeczywistości ten "błąd" może być postrzegany jako funkcja, ponieważ uniemożliwia użytkownikom Twojego kodu przypadkowe użycie instancji, dla których nie testowałeś lub nie planowałeś ("wiem, że to działa dla cola<float> i cola<string>, Jeśli chcesz użyć czegoś innego, powiedz mi najpierw, a will może to zweryfikować działa przed włączeniem.").

Na koniec, w twoim pytaniu są jeszcze trzy drobne literówki w kodzie:]}
  • brakuje #endif na końcu nodo_colaypila.h
  • w coli.h nodo_colaypila<T>* ult, pri; powinno być nodo_colaypila<T> *ult, *pri; - oba są wskaźnikami.
  • nodo_colaypila.cpp: domyślny parametr powinien znajdować się w pliku nagłówkowym nodo_colaypila.h, a nie w tym pliku implementacji.
 404
Author: Aaron McDaid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-06 14:00:18

Będziesz musiał zdefiniować funkcje wewnątrz pliku nagłówkowego.
Nie można oddzielić definicji funkcji szablonu w pliku źródłowym i deklaracji w pliku nagłówkowym.

Gdy szablon jest używany w sposób, który uruchamia jego intstantację, kompilator musi zobaczyć definicję tego konkretnego szablonu. Z tego powodu szablony są często definiowane w pliku nagłówkowym, w którym są deklarowane.

Odniesienie:
C++03 standard, § 14.7.2.4:

Definicja nieeksportowanego szablonu funkcji, nieeksportowanego szablonu funkcji członka lub nieeksportowanej funkcji członka lub elementu danych statycznych szablonu klasy jest obecna w każdej jednostce tłumaczenia , w której jest bezpośrednio utworzona instancja.

EDIT:
Aby wyjaśnić dyskusję na temat komentarzy:
Technicznie rzecz biorąc, istnieją trzy sposoby obejścia tego problemu z linkowaniem:

  • to move definicja do .plik h
  • Dodaj jawne instancje w pliku .cpp.
  • #include Plik .cpp definiujący szablon w pliku .cpp za pomocą szablonu.
Każdy z nich ma swoje plusy i minusy,]}

Przeniesienie definicji do plików nagłówkowych może zwiększyć rozmiar kodu (współczesne Kompilatory mogą tego uniknąć), ale wydłuży czas kompilacji na pewno.

Użycie jawnego podejścia instancjowego wraca do tradycyjnego podejście makro.Inną Wadą jest to, że konieczne jest, aby wiedzieć, które typy szablonów są potrzebne przez program. Dla prostego programu jest to łatwe, ale dla skomplikowanego programu staje się trudne do ustalenia z góry.

Podczas włączania plików cpp jest mylące w tym samym czasie dzieli problemy obu powyższych podejść.

Uważam, że pierwsza metoda jest najłatwiejsza do naśladowania i wdrożenia, a zatem zalecam jej używanie.

 11
Author: Alok Save,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-06 05:39:07

Ten link wyjaśnia, gdzie idziesz źle:

[35.12] dlaczego nie mogę oddzielić definicji mojej klasy templates od jej deklaracji i umieścić ją wewnątrz a .plik cpp?

Umieść definicję swoich konstruktorów, metod destruktorów i innych rzeczy w pliku nagłówkowym, a to rozwiąże problem.

To oferuje inne rozwiązanie:

Jak mogę uniknąć błędów linkera z moimi funkcjami szablonu?

Jednak wymaga to możesz przewidzieć, jak Twój szablon będzie używany i, jako ogólne rozwiązanie, jest intuicyjne. Rozwiązuje to jednak sprawę narożną, w której opracowujesz szablon, który ma być używany przez jakiś wewnętrzny mechanizm i chcesz sprawdzić sposób, w jaki jest używany.

 6
Author: Liam M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-24 08:31:42