Mam kod szablonu, który wolałbym przechowywać w pliku CPP zamiast wbudowanego w nagłówku. Wiem, że można to zrobić, o ile wiesz, jakie typy szablonów będą używane. Na przykład:
plik .h
class foo
{
public:
template <typename T>
void do(const T& t);
};
plik .cpp
template <typename T>
void foo::do(const T& t)
{
// Do something with t
}
template void foo::do<int>(const int&);
template void foo::do<std::string>(const std::string&);
Zwróć uwagę na ostatnie dwa wiersze - funkcja szablonu foo :: do jest używana tylko z ints i std :: strings, więc te definicje oznaczają, że aplikacja się połączy.
Moje pytanie brzmi - czy to paskudny hack, czy będzie to działać z innymi kompilatorami / linkerami? Obecnie używam tego kodu tylko z VS2008, ale będę chciał przenieść się do innych środowisk.
do
jako identyfikatora: ptemplate class foo<int>;template class foo<std::string>;
na końcu pliku .cpp?Odpowiedzi:
Opisany problem można rozwiązać, definiując szablon w nagłówku lub stosując podejście opisane powyżej.
Polecam przeczytanie następujących punktów z C ++ FAQ Lite :
Zagłębiają się w wiele szczegółów na temat tych (i innych) problemów z szablonami.
źródło
Dla innych na tej stronie, którzy zastanawiają się, jaka jest poprawna składnia (podobnie jak ja) w przypadku jawnej specjalizacji szablonów (lub przynajmniej w VS2008), jej następująca ...
W twoim pliku .h ...
I w twoim pliku .cpp
źródło
Ten kod jest dobrze sformułowany. Trzeba tylko zwrócić uwagę, że definicja szablonu jest widoczna w momencie tworzenia wystąpienia. Cytując standard, § 14.7.2.4:
źródło
a.cpp
(definiujące funkcjęa() {}
) ib.cpp
(definiujące funkcjęb() { a() }
), to zostanie to pomyślnie połączone. Jeśli mam rację, to powyższy cytat wydaje się nie mieć zastosowania do typowego przypadku ... czy gdzieś się mylę?inline
funkcjeinline
. Powodem jest to, że bez znormalizowanego ABI w C ++ trudno / nie można zdefiniować efektu, który w przeciwnym razie miałby.Powinno to działać dobrze wszędzie tam, gdzie obsługiwane są szablony. Jawna instancja szablonu jest częścią standardu C ++.
źródło
Twój przykład jest poprawny, ale niezbyt przenośny. Istnieje również nieco czystsza składnia, której można użyć (jak wskazał @ namespace-sid).
Załóżmy, że klasa szablonowa jest częścią biblioteki, która ma być udostępniana. Czy należy kompilować inne wersje klasy szablonowej? Czy opiekun biblioteki powinien przewidywać wszystkie możliwe zastosowania klasy w szablonie?
Alternatywne podejście to niewielka odmiana tego, co masz: dodaj trzeci plik, który jest plikiem implementacji / tworzenia szablonu.
plik foo.h
plik foo.cpp
plik foo-impl.cpp
Jedynym zastrzeżeniem jest to, że musisz powiedzieć kompilatorowi, aby skompilował,
foo-impl.cpp
zamiastfoo.cpp
kompilowania tego drugiego.Oczywiście możesz mieć wiele implementacji w trzecim pliku lub mieć wiele plików implementacji dla każdego typu, którego chcesz użyć.
Umożliwia to znacznie większą elastyczność podczas udostępniania klasy wzorcowej do innych zastosowań.
Ta konfiguracja skraca również czas kompilacji ponownie używanych klas, ponieważ nie rekompilujesz tego samego pliku nagłówka w każdej jednostce tłumaczeniowej.
źródło
foo.cpp
), z których wersje są faktycznie kompilowane (infoo-impl.cpp
) i deklaracji (infoo.h
). Nie podoba mi się, że większość szablonów C ++ jest całkowicie zdefiniowanych w plikach nagłówkowych. Jest to sprzeczne ze standardem par C / C ++c[pp]/h
dla każdej klasy / przestrzeni nazw / jakiejkolwiek używanej grupy. Wydaje się, że ludzie nadal używają monolitycznych plików nagłówkowych tylko dlatego, że ta alternatywa nie jest powszechnie używana ani znana.h/cpp
parę, chociaż musiałem otoczyć oryginalną listę instancji w osłonie dołączania, ale nadal mogłem skompilowaćfoo.cpp
jak zwykle. Nadal jestem całkiem nowy w C ++ i chciałbym wiedzieć, czy to mieszane użycie ma jakieś dodatkowe zastrzeżenie.foo.cpp
ifoo-impl.cpp
. Nie#include "foo.cpp"
wfoo-impl.cpp
pliku; zamiast tego dodaj deklarację,extern template class foo<int>;
abyfoo.cpp
uniemożliwić kompilatorowi tworzenie instancji szablonu podczas kompilacjifoo.cpp
. Upewnij się, że system kompilacji buduje oba.cpp
pliki i przekazuje oba pliki obiektowe do konsolidatora. Ma to wiele zalet: a) jest jasne,foo.cpp
że nie ma instancji; b) zmiany w foo.cpp nie wymagają ponownej kompilacji foo-impl.cpp.foo.cpp
nafoo_impl.h
ifoo-impl.cpp
na justfoo.cpp
. Dodałbym również typedefs dla instancji odfoo.cpp
dofoo.h
, podobnieusing foo_int = foo<int>;
. Sztuką jest zapewnienie użytkownikom dwóch interfejsów nagłówka do wyboru. Kiedy użytkownik potrzebuje wcześniej zdefiniowanej instancji, włączafoo.h
, gdy użytkownik potrzebuje czegoś nie w porządku, to obejmujefoo_impl.h
.To zdecydowanie nie jest nieprzyjemny hack, ale pamiętaj, że będziesz musiał to zrobić (wyraźna specjalizacja szablonu) dla każdej klasy / typu, którego chcesz użyć z danym szablonem. W przypadku WIELU typów żądających utworzenia szablonu może być WIELE plików w pliku .cpp. Aby rozwiązać ten problem, możesz mieć TemplateClassInst.cpp w każdym używanym projekcie, aby mieć większą kontrolę nad typami, które będą tworzone. Oczywiście to rozwiązanie nie będzie idealne (inaczej srebrna kula), ponieważ może to doprowadzić do złamania ODR :).
źródło
W najnowszym standardzie istnieje słowo kluczowe (
export
), które pomogłoby złagodzić ten problem, ale nie jest ono zaimplementowane w żadnym znanym mi kompilatorze innym niż Comeau.Zobacz najczęściej zadawane pytania na ten temat.
źródło
Jest to standardowy sposób definiowania funkcji szablonów. Sądzę, że czytam trzy metody definiowania szablonów. Lub prawdopodobnie 4. Każdy z zalet i wad.
Zdefiniuj w definicji klasy. W ogóle mi się to nie podoba, ponieważ uważam, że definicje klas mają charakter wyłącznie informacyjny i powinny być łatwe do odczytania. Jednak znacznie trudniej jest zdefiniować szablony w klasie niż na zewnątrz. I nie wszystkie deklaracje szablonów są na tym samym poziomie złożoności. Ta metoda sprawia również, że szablon jest prawdziwym szablonem.
Zdefiniuj szablon w tym samym nagłówku, ale poza klasą. Jest to mój preferowany sposób przez większość czasu. Utrzymuje porządek w definicji klasy, szablon pozostaje prawdziwym szablonem. Wymaga to jednak pełnego nazewnictwa szablonów, co może być trudne. Ponadto twój kod jest dostępny dla wszystkich. Ale jeśli potrzebujesz wbudowanego kodu, jest to jedyny sposób. Możesz to również osiągnąć, tworząc plik .INL na końcu definicji klas.
Uwzględnij nagłówek.h oraz implementację.CPP w pliku main.CPP. Myślę, że tak to się robi. Nie będziesz musiał przygotowywać żadnych wcześniejszych instancji, zachowa się jak prawdziwy szablon. Problem, który mam z tym, to, że nie jest to naturalne. Zwykle nie dołączamy plików źródłowych i nie oczekujemy ich. Sądzę, że odkąd załączyłeś plik źródłowy, funkcje szablonu można wstawiać.
Ta ostatnia metoda, która była opublikowana, polega na definiowaniu szablonów w pliku źródłowym, podobnie jak numer 3; ale zamiast dołączać plik źródłowy, tworzymy szablony według potrzebnych nam szablonów. Nie mam problemu z tą metodą i czasem się przydaje. Mamy jeden duży kod, nie można czerpać korzyści z wstawiania go, więc po prostu umieść go w pliku CPP. A jeśli znamy typowe instancje i możemy je wstępnie zdefiniować. To oszczędza nam pisania w zasadzie 5, 10 razy tego samego. Ta metoda ma tę zaletę, że nasz kod jest zastrzeżony. Ale nie polecam umieszczania małych, regularnie używanych funkcji w plikach CPP. Zmniejszy to wydajność Twojej biblioteki.
Uwaga: nie jestem świadomy konsekwencji rozdętego pliku obj.
źródło
Tak, to standardowy sposób
specjalizacjijawnej instancji . Jak już wspomniano, nie można utworzyć tego szablonu z innymi typami.Edycja: poprawione na podstawie komentarza.
źródło
Weźmy jeden przykład, powiedzmy z jakiegoś powodu, że chcesz mieć klasę szablonów:
Jeśli skompilujesz ten kod za pomocą Visual Studio - działa on od razu po rozpakowaniu. gcc wyświetli błąd linkera (jeśli ten sam plik nagłówkowy jest używany z wielu plików .cpp):
Możliwe jest przeniesienie implementacji do pliku .cpp, ale wtedy musisz zadeklarować klasę w ten sposób -
A potem .cpp będzie wyglądać tak:
Bez dwóch ostatnich wierszy w pliku nagłówkowym - gcc będzie działało dobrze, ale Visual Studio wyświetli błąd:
składnia klasy szablonów jest opcjonalna, jeśli chcesz udostępnić funkcję poprzez eksport .dll, ale dotyczy to tylko platformy Windows - test_template.h mógłby wyglądać następująco:
z plikiem .cpp z poprzedniego przykładu.
To jednak powoduje więcej bólu głowy dla linkera, więc zaleca się użycie poprzedniego przykładu, jeśli nie eksportujesz funkcji .dll.
źródło
Czas na aktualizację! Utwórz plik wbudowany (.inl lub prawdopodobnie dowolny inny) i po prostu skopiuj w nim wszystkie swoje definicje. Pamiętaj, aby dodać szablon nad każdą funkcją (
template <typename T, ...>
). Teraz zamiast dołączać plik nagłówkowy do pliku wbudowanego, robi się odwrotnie. Dołącz plik śródliniowy po deklaracji swojej klasy (#include "file.inl"
).Naprawdę nie wiem, dlaczego nikt o tym nie wspominał. Nie widzę żadnych bezpośrednich wad.
źródło
#include "file.inl"
, preprocesor wklei zawartośćfile.inl
bezpośrednio do nagłówka. Bez względu na powód, dla którego chcesz uniknąć implementacji w nagłówku, to rozwiązanie nie rozwiązuje tego problemu.template
definicjami. Rozumiem, dlaczego ludzie chcą to zrobić - aby osiągnąć jak największą parzystość z deklaracjami / definicjami niebędącymi szablonami, aby utrzymać porządek w deklaracji interfejsu itp. - ale nie zawsze jest to warte kłopotu. Chodzi o ocenę kompromisów po obu stronach i wybranie najmniej złego . ... dopóki nie stanienamespace class
się rzeczą: O [ proszę bądź rzeczą ]W podanym przykładzie nie ma nic złego. Ale muszę powiedzieć, że uważam, że przechowywanie definicji funkcji w pliku CPP nie jest efektywne. Rozumiem tylko potrzebę oddzielenia deklaracji i definicji funkcji.
W połączeniu z jawnym tworzeniem instancji klasy biblioteka sprawdzania koncepcji doładowania (BCCL) może pomóc w generowaniu kodu funkcji szablonu w plikach CPP.
źródło
Żadne z powyższych nie działało dla mnie, więc oto jak to rozwiązałeś, moja klasa ma tylko 1 szablon.
.h
.cpp
pozwala to uniknąć błędów linkera i nie trzeba w ogóle wywoływać funkcji tymczasowej
źródło