Funkcja szablonu C++ kompiluje się w nagłówku, ale nie implementuje

[6]}próbuję nauczyć się szablonów i natrafiłem na błąd. Deklaruję niektóre funkcje w pliku nagłówkowym i chcę utworzyć osobny plik implementacji, w którym będą zdefiniowane funkcje. Oto kod wywołujący nagłówek (dum.cpp):

#include <iostream>
#include <vector>
#include <string>
#include "dumper2.h"

int main() {
    std::vector<int> v;
    for (int i=0; i<10; i++) {
        v.push_back(i);
    }
    test();
    std::string s = ", ";
    dumpVector(v,s);
}

Oto działający plik nagłówkowy (dumper2.h):

#include <iostream>
#include <string>
#include <vector>

void test();

template <class T> void dumpVector( std::vector<T> v,std::string sep);

template <class T> void dumpVector(std::vector<T> v, std::string sep) {
    typename std::vector<T>::iterator vi;

    vi = v.begin();
    std::cout << *vi;
    vi++;
    for (;vi<v.end();vi++) {
        std::cout << sep << *vi ;
    }
    std::cout << "\n";
    return;
}

Z implementacją (dumper2.cpp):

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

void test() {
    std::cout << "!olleh dlrow\n";
}

Dziwne jest to, że jeśli przeniosę kod, który definiuje dumpVector z .h do ... plik cpp, Dostaję następujący błąd.

g++ -c dumper2.cpp -Wall -Wno-deprecated
g++ dum.cpp -o dum dumper2.o -Wall -Wno-deprecated
/tmp/ccKD2e3G.o: In function `main':
dum.cpp:(.text+0xce): undefined reference to `void dumpVector<int>(std::vector<int, std::allocator<int> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >)'
collect2: ld returned 1 exit status
make: *** [dum] Error 1

Więc dlaczego to działa w jedną stronę, a nie w drugą? Oczywiście kompilator może znaleźć test(), więc dlaczego nie może znaleźć dumpVector?

Author: Peter Mortensen, 2010-06-15

6 answers

Problem polega na tym, że kompilator nie wie, które wersje szablonu należy utworzyć. Gdy przeniesiesz implementację swojej funkcji do x.cpp jest w innej jednostce tłumaczeniowej niż main.cpp i main.cpp nie może połączyć się z konkretną instancją, ponieważ nie istnieje w tym kontekście. Jest to dobrze znany problem z szablonami C++. Istnieje kilka rozwiązań:

1) po prostu umieść definicje bezpośrednio w .plik h, tak jak wcześniej. To ma plusy i minusy, w tym rozwiązanie problemu (pro), być może uczynienie kodu mniej czytelnym i na niektórych kompilatorach trudniejszym do debugowania (con) i może zwiększenie bloat kodu (con).

2) Umieścić wdrożenie w x.cpp i #include "x.cpp" od wewnątrz x.h. Jeśli to wydaje się dziwne i złe, po prostu pamiętaj, że #include nie robi nic więcej niż odczytuje podany plik i kompiluje go tak, jakby ten plik był częścią x.cpp innymi słowy, robi to dokładnie to, co Rozwiązanie #1 robi powyżej, ale zachowuje są w osobnych plikach fizycznych. Podczas wykonywania tego typu czynności, ważne jest, aby nie próbować kompilować pliku #included na własną rękę. Z tego powodu zwykle daję tego rodzaju plikom rozszerzenie hpp, aby odróżnić je od plików h i od Plików cpp.

Plik: dumper2.h

#include <iostream>
#include <string>
#include <vector>

void test();
template <class T> void dumpVector( std::vector<T> v,std::string sep);
#include "dumper2.hpp"

Plik: dumper2.hpp

template <class T> void dumpVector(std::vector<T> v, std::string sep) {
  typename std::vector<T>::iterator vi;

  vi = v.begin();
  std::cout << *vi;
  vi++;
  for (;vi<v.end();vi++) {
    std::cout << sep << *vi ;
  }
  std::cout << "\n";
  return;

}

3) ponieważ problem polega na tym, że dana instancja dumpVector nie jest znana jednostce tłumaczeniowej, która próbuje jej użyć, możesz wymuś jego konkretną instancję w tej samej jednostce tłumaczenia, w której zdefiniowany jest szablon. Wystarczy dodać to: template void dumpVector<int>(std::vector<int> v, std::string sep);... do pliku, w którym zdefiniowany jest szablon. W ten sposób nie musisz już #include pliku hpp z pliku h:

Plik: dumper2.h

#include <iostream>
#include <string>
#include <vector>

void test();
template <class T> void dumpVector( std::vector<T> v,std::string sep);

Plik: dumper2.cpp

template <class T> void dumpVector(std::vector<T> v, std::string sep) {
  typename std::vector<T>::iterator vi;

  vi = v.begin();
  std::cout << *vi;
  vi++;
  for (;vi<v.end();vi++) {
    std::cout << sep << *vi ;
  }
  std::cout << "\n";
  return;
}

template void dumpVector<int>(std::vector<int> v, std::string sep);

Przy okazji, a na marginesie, twoja funkcja szablonu bierze vector by-value . Możesz nie chcieć tego robić i przejść obok reference lub pointer lub, jeszcze lepiej, podaj Iteratory, aby uniknąć tymczasowego kopiowania całego wektora.

 34
Author: John Dibling,
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
2010-06-14 20:55:39

To właśnie miało osiągnąć słowo kluczowe export (np. poprzez exportw szablonie można umieścić go w pliku źródłowym zamiast nagłówka. Niestety, tylko jeden kompilator (Comeau)naprawdę zaimplementował export całkowicie.

Co do tego, dlaczego inne Kompilatory (w tym gcc) tego nie zaimplementowały, powód jest dość prosty: ponieważ export jest niezwykle trudny do poprawnego zaimplementowania. Kod wewnątrz szablon może zmienić znaczenie (prawie) całkowicie, na podstawie na typie, na którym szablon jest utworzony, więc nie można wygenerować konwencjonalnego pliku obiektowego wyniku kompilacji szablonu. Na przykład, x+y może kompilować do kodu natywnego, takiego jak mov eax, x/add eax, y, gdy tworzy się instancję nad int, ale kompiluje się do wywołania funkcji, jeśli tworzy się instancję nad czymś takim jak std::string, które przeciąża operator+.

Aby wspierać oddzielną kompilację szablonów, musisz wykonać tak zwane dwufazowe wyszukiwanie nazw (tj. szablon i W kontekście tworzenia instancji szablonu). Zazwyczaj kompilator kompiluje szablon do pewnego rodzaju formatu bazy danych, który może przechowywać instancje szablonu nad dowolną kolekcją typów. Następnie dodajesz na etapie między kompilacją a linkowaniem (chociaż może być wbudowany w linker, w razie potrzeby), który sprawdza bazę danych i jeśli nie zawiera kodu dla szablonu tworzonego we wszystkich niezbędnych typach, ponownie wywołuje kompilator do tworzenia instancji nad niezbędnymi typami.

Ze względu na ekstremalny wysiłek, brak wdrożenia itp., Komitet głosował za usunięciem export z następnej wersji standardu C++. Dwie inne, raczej różne, propozycje (moduły i koncepcje) zostały przedstawione, z których każda dostarczyłaby przynajmniej część tego, co export było zamierzone, ale w sposób, który jest (przynajmniej miał nadzieję być) bardziej użyteczny i rozsądny do wdrożenia.

 8
Author: Jerry Coffin,
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
2010-06-14 20:28:41

Parametry szablonu są rozwiązywane w czasie kompilacji.

Kompilator znajdujeh, znajduje pasującą definicję dumpvectora i przechowuje ją. Kompilacja została zakończona .h. następnie kontynuuje przetwarzanie plików i kompilację plików. Kiedy odczytuje implementację dumpvectora wcpp, to zupełnie inna jednostka. Nic nie próbuje utworzyć szablonu w dumper2.cpp, więc kod szablonu jest po prostu pomijany. Kompilator nie spróbuje wszystkich możliwych typów dla szablon, mając nadzieję, że będzie coś przydatnego później dla linkera.

Wtedy, w czasie linkowania, nie została skompilowana Żadna implementacja dumpvectora dla typu int, więc linker nie znajdzie żadnego. Dlatego widzisz ten błąd.

Słowo kluczowe export zostało zaprojektowane, aby rozwiązać ten problem, niestety niewiele kompilatorów go obsługuje. Zachowaj więc implementację z tym samym plikiem co definicja.

 5
Author: Julien Lebosquain,
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
2010-06-14 20:19:26

Funkcja szablonu nie jest funkcją rzeczywistą. Kompilator zamienia funkcję szablonu w funkcję rzeczywistą, gdy napotka użycie tej funkcji. Tak więc cała deklaracja szablonu musi być w zakresie, który znajduje wywołanie DumpVector, w przeciwnym razie nie może wygenerować rzeczywistej funkcji.
O dziwo, wiele książek intro C++ źle to rozumie.

 1
Author: Tim Kay,
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
2010-06-14 20:18:27

Tak właśnie działają szablony w C++, musisz umieścić implementację w nagłówku.

Kiedy deklarujesz/definiujesz funkcję szablonu, kompilator nie może magicznie wiedzieć, z którymi konkretnymi typami chcesz użyć szablonu, więc nie może wygenerować kodu do umieszczenia w a .o plik Jak to może z normalnej funkcji. Zamiast tego polega na generowaniu konkretnej instancji dla typu, gdy widzi użycie tej instancji.

Więc gdy implementacja jest w .C plik, kompilator w zasadzie mówi "Hej, nie ma użytkowników tego szablonu, nie Generuj żadnego kodu". Gdy szablon znajduje się w nagłówku, kompilator jest w stanie zobaczyć użycie w main i faktycznie wygenerować odpowiedni kod szablonu.

 1
Author: Mark B,
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
2010-06-14 20:19:10

Większość kompilatorów nie pozwala na umieszczanie definicji funkcji szablonu w osobnym pliku źródłowym, mimo że jest to technicznie dozwolone przez standard.

Zobacz też:

Http://www.parashift.com/c++ - faq-lite / templates.html#faq-35.12

Http://www.parashift.com/c++ - faq-lite / templates.html#faq-35.14

 0
Author: Cogwheel,
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
2010-06-14 20:20:26