Dzielenie szablonowych klas C++ nahpp/pliki cpp - czy to możliwe?

Dostaję błędy próbując skompilować klasę szablonów C++, która jest podzielona między .hpp i .cpp plik:

$ g++ -c -o main.o main.cpp  
$ g++ -c -o stack.o stack.cpp   
$ g++ -o main main.o stack.o  
main.o: In function `main':  
main.cpp:(.text+0xe): undefined reference to 'stack<int>::stack()'  
main.cpp:(.text+0x1c): undefined reference to 'stack<int>::~stack()'  
collect2: ld returned 1 exit status  
make: *** [program] Error 1  

Oto Mój kod:

Stack.hpp :

#ifndef _STACK_HPP
#define _STACK_HPP

template <typename Type>
class stack {
    public:
            stack();
            ~stack();
};
#endif

Stack.cpp :

#include <iostream>
#include "stack.hpp"

template <typename Type> stack<Type>::stack() {
        std::cerr << "Hello, stack " << this << "!" << std::endl;
}

template <typename Type> stack<Type>::~stack() {
        std::cerr << "Goodbye, stack " << this << "." << std::endl;
}

Main.cpp :

#include "stack.hpp"

int main() {
    stack<int> s;

    return 0;
}

ld jest oczywiście poprawne: symboli nie ma w stack.o.

Odpowiedź na to pytanie nie pomaga, jak już robię to, co mówi.
ten może pomóc, ale ja nie chcę przenosić każdej metody do pliku .hpp-nie powinienem, prawda?

Czy jedynym rozsądnym rozwiązaniem jest przeniesienie wszystkiego w pliku .cpp do pliku .hpp i po prostu dołączenie wszystkiego, zamiast linkowania jako samodzielnego pliku obiektowego? To wydaje się strasznie brzydkie! W takim razie równie dobrze mogę wrócić do poprzedniego stanu i zmienić nazwę stack.cpp na stack.hpp i skończyć z tym.

Author: Community, 2009-11-12

16 answers

Nie jest możliwe zapisanie implementacji klasy szablonu w osobnym pliku cpp i skompilowanie jej. Wszystkie sposoby, aby to zrobić, jeśli ktoś twierdzi, są obejściami naśladującymi użycie oddzielnego pliku cpp, ale praktycznie jeśli zamierzasz napisać bibliotekę klas szablonu i rozpowszechnić ją z plikami nagłówkowymi i lib w celu ukrycia implementacji, po prostu nie jest to możliwe.

Aby wiedzieć dlaczego, przyjrzyjmy się procesowi kompilacji. Pliki nagłówkowe nigdy nie są kompilowane. Są tylko wstępnie przetworzone. Wstępnie przetworzony kod jest następnie łączony z plikiem cpp, który jest faktycznie skompilowany. Teraz, jeśli kompilator musi wygenerować odpowiedni układ pamięci dla obiektu, musi znać typ danych klasy template.

Należy rozumieć, że Klasa template wcale nie jest klasą, ale szablonem dla klasy, której deklaracja i definicja jest generowana przez kompilator w czasie kompilacji po pobraniu informacji typu danych z argumentu. Jako dopóki nie można utworzyć układu pamięci, nie można wygenerować instrukcji do definicji metody. Pamiętaj, że pierwszym argumentem metody class jest operator 'this'. Wszystkie metody klasy są konwertowane na poszczególne metody o nazwie mangling i pierwszym parametrze jako obiekt, na którym operuje. Argument 'this' mówi o rozmiarze obiektu, który zawiera klasę template, jest niedostępny dla kompilatora, chyba że użytkownik utworzy instancję obiektu z poprawny argument typu. W takim przypadku, jeśli umieścisz definicje metod w oddzielnym pliku cpp i spróbujesz je skompilować, sam plik obiektu nie zostanie wygenerowany z informacjami o klasie. Kompilacja nie zawiedzie, wygeneruje plik obiektowy, ale nie wygeneruje żadnego kodu dla klasy szablonu w pliku obiektowym. To jest powód, dla którego linker nie jest w stanie znaleźć symboli w plikach obiektowych i Kompilacja nie powiedzie się.

Jaka jest alternatywa dla ukrycia ważnych szczegóły realizacji? Jak wszyscy wiemy głównym celem oddzielenia interfejsu od implementacji jest ukrywanie szczegółów implementacji w postaci binarnej. W tym miejscu należy oddzielić struktury danych i algorytmy. Twoje klasy szablonów muszą reprezentować tylko struktury danych, a nie algorytmy. Pozwala to na ukrycie cenniejszych szczegółów implementacji w osobnych bibliotekach klas, które nie są templowane, czyli klasach, w których mogłyby pracować na klasach szablonów lub po prostu używać ich do przechowywania danych. Klasa template zawiera mniej kodu do przypisywania, pobierania i ustawiania danych. Reszta pracy byłaby wykonywana przez klasy algorytmów.

Mam nadzieję, że ta dyskusja będzie pomocna.

 132
Author: Sharjith N.,
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-07-13 17:24:51

To jest możliwe, o ile wiesz, jakich instancji będziesz potrzebować.

Dodaj następujący kod na końcu stosu.cpp i będzie działać:

template class stack<int>;

Wszystkie metody nie-szablonowe stosu zostaną utworzone, a krok łączenia będzie działał dobrze.

 75
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
2014-06-10 19:10:37

Możesz to zrobić w ten sposób

// xyz.h
#ifndef _XYZ_
#define _XYZ_

template <typename XYZTYPE>
class XYZ {
  //Class members declaration
};

#include "xyz.cpp"
#endif

//xyz.cpp
#ifdef _XYZ_
//Class definition goes here

#endif

To zostało omówione w Daniweb

Również w FAQ ale używając słowa kluczowego C++ export.

 8
Author: Sadanand,
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-04-03 12:06:41

Nie, to niemożliwe. Nie bez słowa kluczowego export, które tak naprawdę nie istnieje.

Najlepsze, co możesz zrobić, to umieścić swoje implementacje funkcji w ".tcc "lub".TPP " Plik, oraz # include the .plik tcc na końcu twojego .plik hpp. Jest to jednak tylko kosmetyczne; to nadal to samo, co implementacja wszystkiego w plikach nagłówkowych. Jest to po prostu cena, którą płacisz za korzystanie z szablonów.

 6
Author: Charles Salvia,
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-11-12 17:53:43

Uważam, że istnieją dwa główne powody, dla których próbuje się rozdzielić kod szablonu na nagłówek i cpp:

Jeden jest dla zwykłej elegancji. Wszyscy lubimy pisać kod, który jest wasy do czytania, zarządzania i jest wielokrotnego użytku później.

Inne to skrócenie czasu kompilacji.

Obecnie (jak zawsze) koduję oprogramowanie symulacyjne w połączeniu z OpenCL i lubimy zachować kod, aby mógł być uruchamiany za pomocą typów float (cl_float) lub double (cl_double) w zależności od potrzeb. W tej chwili odbywa się to za pomocą # define REAL na początku kodu, ale nie jest to zbyt eleganckie. Zmiana wymaganej precyzji wymaga rekompilacji aplikacji. Ponieważ nie ma prawdziwych typów run-time, musimy z tym żyć na razie. Na szczęście jądra OpenCL są kompilowane w trybie runtime, A prosty rozmiar (REAL)pozwala nam odpowiednio zmienić środowisko uruchomieniowe jądra.

Znacznie większym problemem jest to, że mimo że aplikacja jest modułowa, to przy tworzeniu klas pomocniczych (takie jak te, które wstępnie obliczają stałe symulacji) również muszą być template. Wszystkie te klasy pojawiają się przynajmniej raz na szczycie drzewa zależności klas, ponieważ ostateczna Symulacja klasy szablonu będzie miała instancję jednej z tych klas fabrycznych, co oznacza, że praktycznie za każdym razem, gdy wprowadzę drobną zmianę do klasy fabrycznej, całe oprogramowanie musi zostać przebudowane. To bardzo irytujące, ale nie mogę znaleźć lepszego rozwiązania.

 3
Author: Meteorhead,
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-11-09 09:05:09

Czasami jest możliwe, aby większość implementacji była ukryta w pliku cpp, jeśli można wyodrębnić wspólną funkcjonalność wszystkich parametrów szablonu do klasy non-template (ewentualnie type-unsafe). Wtedy nagłówek będzie zawierał wywołania przekierowania do tej klasy. Podobne podejście stosuje się w walce z problemem "wzdęcia szablonu".

 2
Author: Konstantin Tenzin,
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-11-12 18:07:00

Jeśli wiesz, z jakimi typami będzie używany twój stos, możesz utworzyć ich instancję w pliku cpp i zachować tam cały odpowiedni kod.

Możliwe jest również eksportowanie tych plików do bibliotek DLL (!) ale jest to dość trudne, aby uzyskać poprawną składnię(specyficzne dla MS kombinacje __declspec (dllexport) i słowa kluczowego export).

Użyliśmy tego w lib math / geom, który templował double / float, ale miał sporo kodu. (W tym czasie szukałem go w googlach, nie mam tego kodu ale dzisiaj.)

 2
Author: Macke,
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-11-12 18:30:58

Problem polega na tym, że szablon nie generuje rzeczywistej klasy, tylko szablon mówi kompilatorowi, jak wygenerować klasę. Musisz wygenerować konkretną klasę.

Łatwym i naturalnym sposobem jest umieszczenie metod w pliku nagłówkowym. Ale jest inny sposób.

W Twoim .plik cpp, jeśli masz odniesienie do każdej instancji szablonu i metody, której potrzebujesz, kompilator wygeneruje je tam do użycia w całym projekcie.

Nowe stack.cpp:

#include <iostream>
#include "stack.hpp"
template <typename Type> stack<Type>::stack() {
        std::cerr << "Hello, stack " << this << "!" << std::endl;
}
template <typename Type> stack<Type>::~stack() {
        std::cerr << "Goodbye, stack " << this << "." << std::endl;
}
static void DummyFunc() {
    static stack<int> stack_int;  // generates the constructor and destructor code
    // ... any other method invocations need to go here to produce the method code
}
 2
Author: Mark Ransom,
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-11-12 19:00:41

Musisz mieć wszystko w pliku hpp. Problem polega na tym, że klasy nie są faktycznie tworzone, dopóki kompilator nie zobaczy, że są potrzebne przez inny plik cpp - więc musi mieć cały kod dostępny do skompilowania klasy szablonowej w tym czasie.

Jedną z rzeczy, które zwykle robię, jest próba podzielenia moich szablonów na ogólną część nie-szablonową (którą można podzielić między cpp/hpp) i część szablonu specyficzną dla typu, która dziedziczy klasę nie-szablonową.

 1
Author: Aaron,
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-11-12 17:44:16

Tylko jeśli #include "stack.cpp Na końcu stack.hpp. Polecam takie podejście tylko wtedy, gdy implementacja jest stosunkowo duża i jeśli zmienisz nazwę .plik cpp do innego rozszerzenia, aby odróżnić go od zwykłego kodu.

 1
Author: lyricat,
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-11-12 17:46:32

To dość stare pytanie, ale myślę, że warto obejrzeć prezentację Arthura o ' Dwyera na cppcon 2016 . Dobre wytłumaczenie, dużo tematów poruszonych, obowiązkowa obserwacja.

 1
Author: FreeYourSoul,
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-07-09 10:29:07

Ponieważ szablony są kompilowane, gdy jest to wymagane, wymusza to ograniczenie dla projektów z wieloma plikami: implementacja (definicja) klasy lub funkcji szablonu musi znajdować się w tym samym pliku, co jej deklaracja. Oznacza to, że nie możemy oddzielić interfejsu w osobnym pliku nagłówkowym i że musimy uwzględnić zarówno interfejs, jak i implementację w każdym pliku, który używa szablonów.

 0
Author: ChadNC,
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-11-12 17:43:03

Inną możliwością jest zrobienie czegoś takiego:

#ifndef _STACK_HPP
#define _STACK_HPP

template <typename Type>
class stack {
    public:
            stack();
            ~stack();
};

#include "stack.cpp"  // Note the include.  The inclusion
                      // of stack.h in stack.cpp must be 
                      // removed to avoid a circular include.

#endif
Nie podoba mi się ta sugestia jako kwestia stylu, ale może Ci pasować.
 0
Author: luke,
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-11-12 17:49:04

Słowo kluczowe 'export' jest sposobem oddzielenia implementacji szablonu od deklaracji szablonu. Został on wprowadzony w standardzie C++ bez istniejącej implementacji. Z czasem zaimplementowało go tylko kilka kompilatorów. Przeczytaj szczegółowe informacje w Inform IT article on export

 0
Author: Shailesh Kumar,
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-11-12 17:51:32

1) Zapamiętaj główny powód oddzielenia .h i .pliki cpp ma ukryć implementację klasy jako oddzielnie skompilowany Kod Obj, który może być połączony z kodem użytkownika, który zawiera .h klasy.

2) Klasy Nie-szablonowe mają wszystkie zmienne konkretnie i konkretnie zdefiniowane w .h i .pliki cpp. Tak więc kompilator będzie miał potrzebne informacje o wszystkich typach danych używanych w klasie przed kompilacją/tłumaczeniem generowania kodu obiektowego / maszynowego Klasy szablonów nie mają informacja o konkretnym typie danych przed użytkownikiem klasy tworzy instancję obiektu przekazującego wymagany typ danych:

        TClass<int> myObj;

3) dopiero po tej instancji, kompilator generuje określoną wersję klasy szablonu, aby pasowała do przekazywanych typów danych.

4) dlatego, .cpp nie może być skompilowany oddzielnie bez znajomości użytkownika określonego typu danych. Więc musi pozostać w kodzie źródłowym".h " dopóki użytkownik nie określi wymaganego typu danych, wtedy może być wygenerowane do określonego typu danych, a następnie skompilowane

 0
Author: Aaron01,
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
2014-04-27 18:39:46

Pracuję z Visual studio 2010, Jeśli chcesz podzielić pliki na .h i .cpp, Dołącz nagłówek cpp na końcu .plik h

 -3
Author: Ahmad,
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-06-26 14:00:40