Dlaczego szablony mogą być zaimplementowane tylko w pliku nagłówkowym?

Cytat z The C++ standard library: a tutorial and handbook :

Jedynym przenośnym sposobem użycia szablonów w tej chwili jest zaimplementowanie ich w plikach nagłówkowych za pomocą funkcji wbudowanych.

Dlaczego tak jest?

(Wyjaśnienie: pliki nagłówkowe nie są tylko przenośnym rozwiązaniem. Ale są to najwygodniejsze przenośne rozwiązanie.)

Author: Aaron McDaid, 2009-01-30

14 answers

It is not need to put the implementation in the header file, see the alternative solution at the end of this answer.

W każdym razie, powodem niepowodzenia Twojego kodu jest to, że podczas tworzenia instancji szablonu kompilator tworzy nową klasę z podanym argumentem szablonu. Na przykład:

template<typename T>
struct Foo
{
    T bar;
    void doSomething(T param) {/* do stuff using T */}
};

// somewhere in a .cpp
Foo<int> f; 

Podczas czytania tej linii kompilator utworzy nową klasę (nazwijmy ją FooInt), która jest równoważna następującej wartości:

struct FooInt
{
    int bar;
    void doSomething(int param) {/* do stuff using int */}
}

W konsekwencji kompilator musi mieć dostęp do implementacji metod, aby utworzyć ich instancję z argumentem szablonu (w tym przypadku int). Gdyby tych implementacji nie było w nagłówku, nie byłyby one dostępne, a zatem kompilator nie byłby w stanie utworzyć instancji szablonu.

Powszechnym rozwiązaniem jest zapisanie deklaracji szablonu w pliku nagłówkowym, a następnie zaimplementowanie klasy w pliku implementacji (na przykład .tpp), oraz załączyć ten plik implementacyjny na końcu nagłówek.

// Foo.h
template <typename T>
struct Foo
{
    void doSomething(T param);
};

#include "Foo.tpp"

// Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}

W ten sposób implementacja jest nadal oddzielona od deklaracji, ale jest dostępna dla kompilatora.

Innym rozwiązaniem jest oddzielenie implementacji i jawne utworzenie instancji wszystkich szablonów, których potrzebujesz:

// Foo.h

// no implementation
template <typename T> struct Foo { ... };

//----------------------------------------    
// Foo.cpp

// implementation of Foo's methods

// explicit instantiations
template class Foo<int>;
template class Foo<float>;
// You will only be able to use Foo with int or float

Jeśli moje wyjaśnienie nie jest wystarczająco jasne, możesz rzucić okiem na C++ Super-FAQ na ten temat.

 1245
Author: Luc Touraille,
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-08-07 13:06:55

Dużo poprawnych odpowiedzi tutaj, ale chciałem dodać to (dla kompletności):

Jeśli na dole pliku implementacji cpp wykonasz jawną instancję wszystkich typów, z którymi będzie używany szablon, linker będzie mógł je znaleźć jak zwykle.

Edit: dodanie przykładu jawnej instancji szablonu. Używany po zdefiniowaniu szablonu i zdefiniowaniu wszystkich funkcji Członkowskich.

template class vector<int>;

To stworzy (i tym samym udostępni linker) klasa i wszystkie jej funkcje Członkowskie (tylko). Podobna składnia działa w przypadku funkcji szablonu, więc jeśli masz przeciążenia operatorów innych niż członkowie, być może będziesz musiał zrobić to samo dla tych funkcji.

Powyższy przykład jest dość bezużyteczny, ponieważ wektor jest w pełni zdefiniowany w nagłówkach, z wyjątkiem przypadku, gdy wspólny plik nagłówka (wstępnie skompilowany nagłówek?) używa extern template class vector<int>, aby nie tworzyć instancji we wszystkich innych (1000?) pliki używające wektora.

 206
Author: MaHuJa,
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
2013-02-14 14:26:27

To ze względu na wymóg oddzielnej kompilacji i dlatego, że szablony są polimorfizmem w stylu instancjacji.

Przyjrzyjmy się bliżej betonowi, aby uzyskać wyjaśnienie. Powiedzmy, że mam następujące pliki:

  • foo.h
    • deklaruje interfejs class MyClass<T>
  • foo.cpp
    • definiuje implementację class MyClass<T>
  • bar.cpp
    • używa MyClass<int>

Oddzielna kompilacja oznacza, że powinienem być w stanie skompilować foo.cpp niezależnie od bar.cpp . Kompilator wykonuje całą ciężką pracę analizy, optymalizacji i generowania kodu na każdej jednostce kompilacji całkowicie niezależnie; nie musimy wykonywać analizy całego programu. Tylko linker musi obsługiwać cały program na raz, a praca linkera jest znacznie łatwiejsza.

Bar.cpp nie musi nawet istnieć, gdy kompiluję foo.cpp , ale powinienem / align= "left" / o miałem już razem z bar.o dopiero co wyprodukowałem, bez potrzeby rekompilacji foo.cpp . foo.cpp może być nawet skompilowany do dynamicznej biblioteki, rozpowszechniany gdzie indziej bez foo.cpp , a linked with code piszą lata po tym jak napisałem foo.cpp .

"polimorfizm w stylu Instancjowym" oznacza, że szablon MyClass<T> nie jest tak naprawdę klasą generyczną, którą można skompilować do kod, który może działać dla dowolnej wartości T. To dodałoby dodatkowe koszty, takie jak boks, konieczność przekazywania wskaźników funkcji do alokatorów i konstruktorów itp. Intencją szablonów C++ jest uniknięcie konieczności pisania prawie identycznych class MyClass_int, class MyClass_float, itd., ale aby nadal móc skończyć z skompilowanym kodem, który jest w większości tak, jakbyśmy mieli napisane każdą wersję osobno. Więc szablon jest dosłownie szablonem; szablon klasy jest a nie klasą, to przepis na stworzenie nowa klasa dla każdego T, z którym się spotykamy. Szablon nie może być skompilowany do kodu, tylko wynik tworzenia instancji szablonu może być skompilowany.

Więc kiedy foo.cpp jest skompilowany, kompilator nie widzi paska .cpp aby wiedzieć, że MyClass<int> jest potrzebne. Może zobaczyć szablon MyClass<T>, ale nie może emitować do tego kodu (to szablon, Nie Klasa). I kiedy bar.cpp jest skompilowany, kompilator widzi, że musi utworzyć MyClass<int>, ale nie widzi szablonu MyClass<T> (tylko jego interfejs w foo.h ), więc nie może go stworzyć.

If foo.cpp sam używa MyClass<int>, następnie kod do tego zostanie wygenerowany podczas kompilacji foo.cpp, więc kiedy bar.o jest połączone z foo.o mogą być Podłączone i będą działać. Możemy użyć tego faktu, aby pozwolić na zaimplementowanie skończonego zbioru instancji szablonów w a .plik cpp pisząc pojedynczy szablon. Ale nie ma sposobu na bar.cpp aby użyć szablonu jako szablon i tworzy instancje na dowolnych typach, które lubi; może używać tylko wcześniej istniejących wersji klasy template, które autor foo.cpp

Można by pomyśleć, że kompilator podczas kompilacji szablonu powinien "wygenerować wszystkie wersje", a te, które nigdy nie są używane, są filtrowane podczas łączenia. Pomijając ogromne koszty i skrajne trudności, jakie napotkałoby takie podejście, ponieważ funkcje "type modifier", takie jak wskaźniki i tablice pozwalają nawet tylko wbudowanym typom, aby dać początek nieskończonej liczbie typów, co się stanie, gdy TERAZ rozszerzę mój program dodając:

  • baz.cpp
    • deklaruje i wdraża class BazPrivate, i używa MyClass<BazPrivate>

Nie ma możliwości, żeby to zadziałało, chyba że my albo

  1. trzeba przekompilować foo.cpp za każdym razem, gdy zmieniamy dowolny inny plik w programie , na wypadek gdyby dodał nową instancję MyClass<T>
  2. baz.cpp zawiera (prawdopodobnie poprzez header includes) pełny szablon MyClass<T>, dzięki czemu kompilator może wygenerować MyClass<BazPrivate> podczas kompilacji baz.cpp .

Nikt nie lubi (1), ponieważ systemy kompilacji całego programu zajmują wieczność, aby skompilować i ponieważ uniemożliwia to dystrybucję skompilowanych bibliotek bez kodu źródłowego. Więc mamy (2) zamiast.

 179
Author: Ben,
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
2013-05-11 11:26:56

Szablony muszą być utworzone z instancji przez kompilator, zanim zostaną skompilowane do kodu obiektowego. Tę instancję można osiągnąć tylko wtedy, gdy argumenty szablonu są znane. Teraz wyobraź sobie scenariusz, w którym funkcja szablonu jest zadeklarowana w a.h, zdefiniowana w a.cpp i używana w b.cpp. Kiedy a.cpp jest kompilowany, niekoniecznie wiadomo, że nadchodząca kompilacja b.cpp będzie wymagała wystąpienia szablonu, nie mówiąc już o konkretnej instancji. Więcej nagłówka i pliki źródłowe, sytuacja może szybko się skomplikować.

Można argumentować, że Kompilatory mogą być mądrzejsze do "patrzenia w przyszłość" dla wszystkich zastosowań szablonu, ale jestem pewien, że nie byłoby trudno stworzyć rekurencyjne lub inaczej skomplikowane scenariusze. AFAIK, Kompilatory tak nie wyglądają. Jak zauważył Anton, niektóre Kompilatory wspierają jawne deklaracje eksportowe instancji szablonów, ale nie wszystkie Kompilatory to obsługują (jeszcze?).

 66
Author: David Hanak,
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
2016-08-04 07:41:11

W rzeczywistości wersje standardu C++ przed C++11 definiowały słowo kluczowe 'export', które umożliwiałoby po prostu deklarowanie szablonów w pliku nagłówkowym i implementowanie ich gdzie indziej.

Niestety, żaden z popularnych kompilatorów nie zaimplementował tego słowa kluczowego. Jedyne o czym wiem to frontend napisany przez Edison Design Group, który jest używany przez kompilator Comeau C++. Wszyscy inni nalegali, aby pisać szablony w plikach nagłówkowych, potrzebując definicji kodu dla prawidłowej instancji (jak już zauważyli inni).

W rezultacie Komitet Standardów ISO C++ postanowił usunąć export funkcję szablonów zaczynających się od C++11.

 49
Author: DevSolar,
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-02-10 21:21:58

Chociaż standard C++ nie ma takiego wymogu, niektóre Kompilatory wymagają, aby wszystkie szablony funkcji i klas były dostępne w każdej używanej jednostce tłumaczeniowej. W efekcie w przypadku tych kompilatorów ciała funkcji szablonu muszą być udostępnione w pliku nagłówkowym. To repeat: oznacza to, że Kompilatory te nie pozwolą na zdefiniowanie ich w plikach innych niż nagłówki, takich jak .pliki cpp

Istnieje export słowo kluczowe, które ma złagodzić ten problem, ale to nigdzie nie jest blisko bycia przenośnym.

 32
Author: Anton Gogolev,
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
2013-09-19 17:16:55

Szablony muszą być używane w nagłówkach, ponieważ kompilator musi tworzyć instancje różnych wersji kodu, w zależności od parametrów podanych/wydedukowanych dla parametrów szablonu. Pamiętaj, że szablon nie reprezentuje kodu bezpośrednio, ale szablon dla kilku wersji tego kodu. Gdy kompilujesz funkcję nie-szablonową w pliku .cpp, kompilujesz konkretną funkcję / klasę. Nie dotyczy to szablonów, które mogą być tworzone z różnymi typami, a mianowicie z konkretnymi kod musi być emitowany podczas zastępowania parametrów szablonu konkretnymi typami.

Istniała funkcja ze słowem kluczowym export, która miała być używana do oddzielnej kompilacji. Funkcja export jest przestarzała w C++11 i, AFAIK, zaimplementował ją tylko jeden kompilator. Nie powinieneś używać export. Oddzielna kompilacja nie jest możliwa w C++ lub C++11, ale może w C++17, jeśli uda się to zrobić, możemy mieć jakiś sposób oddzielnej kompilacji.

Aby oddzielna Kompilacja Była osiągnięte, musi być możliwe osobne sprawdzenie ciała szablonu. Wydaje się, że rozwiązanie jest możliwe dzięki koncepcjom. Zapraszamy do zapoznania się z artykułem prezentowanym ostatnio na spotkanie Komisji standardów. Myślę, że nie jest to jedyny wymóg, ponieważ nadal musisz utworzyć instancję kodu szablonu w kodzie użytkownika.

Oddzielny problem kompilacji szablonów myślę, że jest to również problem, który pojawia się w związku z migracją do modułów, która jest obecnie w trakcie pracy.

 26
Author: Germán Diago,
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
2013-05-12 16:48:42

Oznacza to, że najbardziej przenośnym sposobem definiowania implementacji metod klas szablonów jest zdefiniowanie ich wewnątrz definicji klasy szablonów.

template < typename ... >
class MyClass
{

    int myMethod()
    {
       // Not just declaration. Add method implementation here
    }
};
 13
Author: Benoît,
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
2009-02-10 21:44:15

Mimo że powyżej jest wiele dobrych wyjaśnień, brakuje mi praktycznego sposobu na rozdzielenie szablonów na nagłówki i body.
Moim głównym zmartwieniem jest uniknięcie rekompilacji wszystkich użytkowników szablonu, kiedy zmieniam jego definicję.
Posiadanie wszystkich instancji szablonu w ciele szablonu nie jest dla mnie realnym rozwiązaniem, ponieważ autor szablonu może nie wiedzieć wszystkiego, czy jego użycie, a użytkownik szablonu może nie mieć prawa do jego modyfikacji.
Przyjęłam następujące podejście, które działa również dla starszych kompilatorów (gcc 4.3.4, aCC A. 03.13).

Dla każdego użycia szablonu jest typedef we własnym pliku nagłówkowym (wygenerowanym z modelu UML). Jego ciało zawiera instancję (która kończy się w bibliotece, która jest połączona na końcu).
Każdy użytkownik szablonu zawiera ten plik nagłówkowy i używa typedef.

Schemat przykład:

MyTemplate.h:

#ifndef MyTemplate_h
#define MyTemplate_h 1

template <class T>
class MyTemplate
{
public:
  MyTemplate(const T& rt);
  void dump();
  T t;
};

#endif

MyTemplate.cpp:

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

template <class T>
MyTemplate<T>::MyTemplate(const T& rt)
: t(rt)
{
}

template <class T>
void MyTemplate<T>::dump()
{
  cerr << t << endl;
}

Myinstantatedtemplate.h:

#ifndef MyInstantiatedTemplate_h
#define MyInstantiatedTemplate_h 1
#include "MyTemplate.h"

typedef MyTemplate< int > MyInstantiatedTemplate;

#endif

Myinstantatedtemplate.cpp:

#include "MyTemplate.cpp"

template class MyTemplate< int >;

Main.cpp:

#include "MyInstantiatedTemplate.h"

int main()
{
  MyInstantiatedTemplate m(100);
  m.dump();
  return 0;
}

W ten sposób tylko instancje szablonu będą musiały zostać przekompilowane, a nie wszyscy użytkownicy szablonu (i zależności).

 9
Author: lafrecciablu,
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
2016-05-12 14:08:56

To jest dokĹ 'adnie poprawne, poniewaĹź kompilator musi wiedzieć, jaki typ ma przydziaĺ'. Więc klasy szablonów, funkcje, wyliczenia itp.. musi być zaimplementowane, jak również w pliku nagłówkowym, jeśli ma być upublicznione lub część biblioteki (statyczne lub dynamiczne), ponieważ pliki nagłówkowe nie są kompilowane w przeciwieństwie do plików C / cpp, które są. Jeśli kompilator nie zna typu is, Nie może go skompilować. W. Net może, ponieważ wszystkie obiekty wywodzą się z klasy Object. To nie jest .Net.

 6
Author: Robert,
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
2011-09-17 12:27:39

Jeśli problemem jest dodatkowy czas kompilacji i rozmiar binarny, wytworzony przez kompilację .h jako część wszystkich .Moduły cpp za jego pomocą, w wielu przypadkach można sprawić, że Klasa template zejdzie z nie-templatowanej klasy bazowej dla nie zależnych od typu części interfejsu, a ta klasa bazowa może mieć swoją implementację w .plik cpp.

 6
Author: Eric Shaw,
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
2016-07-27 05:01:10

Sposób na oddzielną implementację jest następujący.

//inner_foo.h

template <typename T>
struct Foo
{
    void doSomething(T param);
};


//foo.tpp
#include "inner_foo.h"
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}


//foo.h
#include <foo.tpp>

//main.cpp
#include <foo.h>

Inner_foo ma deklaracje forward. foo.TPP ma implementację i zawiera inner_foo.h; i foo.h będzie miał tylko jedną linię, aby włączyć foo.tpp.

W czasie kompilacji zawartość foo.H są kopiowane do foo.tpp, a następnie cały plik jest kopiowany do foo.h po czym kompiluje. W ten sposób nie ma żadnych ograniczeń, a nazewnictwo jest spójne, w zamian za jeden dodatkowy plik.

Robię to, ponieważ statyczne analizator kodu łamie się, gdy nie widzi deklaracji forward klasy w *.tpp. Jest to denerwujące podczas pisania kodu w dowolnym IDE lub używania YouCompleteMe lub innych.

 2
Author: Pranay,
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
2017-05-13 01:42:11

Kompilator wygeneruje kod dla każdej instancji szablonu, gdy używasz szablonu podczas kompilacji. W procesie kompilacji i łączenia .pliki cpp są konwertowane do czystego kodu obiektowego lub maszynowego, który w nich zawiera odniesienia lub niezdefiniowanych symboli, ponieważ .pliki h, które są zawarte w głównym.cpp nie ma jeszcze implementacji. Są one gotowe do połączenia z innym plikiem obiektowym, który definiuje implementację dla Twojego szablonu i tym samym masz pełne a. out wykonywalny. Ponieważ jednak szablony muszą być przetwarzane w kroku kompilacji, aby wygenerować kod dla każdej instancji szablonu, którą wykonujesz w głównym programie, łączenie nie pomoże, ponieważ kompilowanie głównego.cpp do centrali.o, a następnie kompilowanie szablonu .cpp do szablonu.o a potem linkowanie nie osiągnie celu szablonów, bo linkuję różne instancje szablonów do tej samej implementacji szablonów! A szablony mają robić odwrotnie tzn. mieć jeden implementacja, ale pozwala na wiele dostępnych instancji za pomocą jednej klasy.

To znaczy typename T get jest zastępowany podczas kompilacji, a nie linkowania, więc jeśli próbuję skompilować szablon bez zastępowania {[1] } jako konkretnego typu wartości, więc to nie zadziała, ponieważ taka jest definicja szablonów, jest to proces kompilacji w czasie, a btw meta-programowanie polega na użyciu tej definicji.

 1
Author: Moshe Rabaev,
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
2017-03-16 02:16:18

Żeby dodać coś godnego uwagi. Metody klasy template można definiować w pliku implementacji, gdy nie są szablonami funkcji.


MyQueue.hpp:
template <class T> 
class QueueA {
    int size;
    ...
public:
    template <class T> T dequeue() {
       // implementation here
    }

    bool isEmpty();

    ...
}    

MyQueue.cpp:
// implementation of regular methods goes like this:
template <class T> bool QueueA<T>::isEmpty() {
    return this->size == 0;
}


main()
{
    QueueA<char> Q;

    ...
}
 1
Author: Nik-Lz,
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-07-19 00:49:05