Zawsze biorąc pod uwagę, że następujący nagłówek, zawierający moją klasę z szablonu, jest zawarty w co najmniej dwóch .CPP
plikach, ten kod kompiluje się poprawnie:
template <class T>
class TClass
{
public:
void doSomething(std::vector<T> * v);
};
template <class T>
void TClass<T>::doSomething(std::vector<T> * v) {
// Do something with a vector of a generic T
}
template <>
inline void TClass<int>::doSomething(std::vector<int> * v) {
// Do something with a vector of int's
}
Ale zwróć uwagę na inline w metodzie specjalizacji. Jest to wymagane, aby uniknąć błędu konsolidatora (w VS2008 jest to LNK2005), ponieważ metoda jest definiowana więcej niż raz. Rozumiem to, ponieważ AFAIK pełna specjalizacja szablonu jest tym samym, co prosta definicja metody.
Jak więc to usunąć inline
? Kod nie powinien być powielany przy każdym jego użyciu. Przeszukałem Google, przeczytałem kilka pytań tutaj w SO i wypróbowałem wiele sugerowanych rozwiązań, ale żadnego nie udało się zbudować (przynajmniej nie w VS 2008).
Dzięki!
Odpowiedzi:
Podobnie jak w przypadku prostych funkcji, możesz użyć deklaracji i implementacji. Umieść w swojej deklaracji nagłówka:
template <> void TClass<int>::doSomething(std::vector<int> * v);
i umieść implementację w jednym ze swoich plików cpp:
template <> void TClass<int>::doSomething(std::vector<int> * v) { // Do somtehing with a vector of int's }
Nie zapomnij usunąć inline (zapomniałem i pomyślałem, że to rozwiązanie nie zadziała :)). Sprawdzono w VC ++ 2005
źródło
inline
podczas kopiowania / wklejania. Tak to zadziałało!Musisz przenieść definicję specjalizacji do pliku CPP. Specjalizacja funkcji składowej klasy szablonu jest dozwolona, nawet jeśli funkcja nie jest zadeklarowana jako szablon.
źródło
Nie ma powodu, aby usuwać wbudowane słowo kluczowe.
W żaden sposób nie zmienia to znaczenia kodu.
źródło
inline
słowo kluczowe powoduje, że funkcja jest faktycznie wbudowana (standard mówi, że kompilator powinien to potraktować jako wskazówkę), to tych dodatkowych kopii nie można usunąć. Jest to jednak tylko wskazówka do inline (jej głównym efektem jest stwierdzenie "nie generuj błędów przy kolizjach łączy w określony sposób")Jeśli chcesz usunąć inline z jakiegokolwiek powodu, rozwiązanie maxim1000 jest całkowicie poprawne.
Jednak w Twoim komentarzu wydaje się, że wierzysz, że słowo kluczowe inline oznacza, że funkcja z całą zawartością jest zawsze wstawiana, ale AFAIK jest to w rzeczywistości bardzo zależne od optymalizacji twojego kompilatora.
Cytowanie z C ++ FAQ
Tak więc, jeśli nie wiesz, że ta funkcja faktycznie naduży Twój plik wykonywalny lub jeśli nie chcesz go usunąć z nagłówka definicji szablonu z innych powodów, możesz pozostawić go tam, gdzie jest bez żadnych szkód
źródło
Chciałbym dodać, że nadal istnieje dobry powód, aby pozostawić
inline
tam słowo kluczowe, jeśli zamierzasz pozostawić również specjalizację w pliku nagłówkowym.Źródła: https://stackoverflow.com/a/4445772/1294184
źródło
To trochę OT, ale pomyślałem, że zostawię to tutaj na wypadek, gdyby pomogło to komuś innemu. Szukałem w Google o specjalizacji szablonów, która doprowadziła mnie do tego, i chociaż odpowiedź @ maxim1000 jest prawidłowa i ostatecznie pomogła mi rozwiązać moje problemy, nie sądziłem, że jest to zbyt jasne.
Moja sytuacja jest trochę inna (ale na tyle podobna, żeby zostawić tę odpowiedź, jak myślę) niż PO. Zasadniczo używam biblioteki innej firmy z różnymi rodzajami klas, które definiują „typy statusu”. Sercem tych typów jest po prostu
enum
s, ale wszystkie klasy dziedziczą po wspólnym (abstrakcyjnym) rodzicu i zapewniają różne funkcje użytkowe, takie jak przeciążanie operatora istatic toString(enum type)
funkcja. Każdy statusenum
różni się od siebie i nie ma ze sobą związku. Na przykład, jedenenum
ma polaNORMAL, DEGRADED, INOPERABLE
, inny maAVAILBLE, PENDING, MISSING
itd. Moje oprogramowanie zarządza różnymi rodzajami statusów różnych komponentów. Doszło do tego, że chciałem do tego wykorzystać tetoString
funkcjeenum
klasy, ale ponieważ są abstrakcyjne, nie mogłem ich bezpośrednio utworzyć. Mógłbym przedłużyć każdą klasę, z której chciałem korzystać, ale ostatecznie zdecydowałem się stworzyćtemplate
klasę, w którejtypename
byłby taki konkretny status, na którymenum
mi zależy. Prawdopodobnie można dyskutować na temat tej decyzji, ale czułem, że było to o wiele mniej pracy niż rozszerzenie każdejenum
klasy abstrakcyjnej o własną, niestandardową i implementowanie funkcji abstrakcyjnych. I oczywiście w moim kodzie chciałem mieć możliwość wywołania.toString(enum type)
i wydrukowania reprezentacji ciąguenum
. Ponieważ wszystkieenum
były zupełnie niepowiązane, każdy z nich miał swoje własnetoString
funkcje, które (po pewnych badaniach, których się dowiedziałem) musiały być wywoływane przy użyciu specjalizacji szablonowej. To doprowadziło mnie tutaj. Poniżej znajduje się MCVE tego, co musiałem zrobić, aby to działało poprawnie. I faktycznie moje rozwiązanie było trochę inne niż @ maxim1000.To jest (znacznie uproszczony) plik nagłówkowy dla
enum
s. W rzeczywistości każdaenum
klasa została zdefiniowana w swoim własnym pliku. Ten plik reprezentuje pliki nagłówkowe, które są dostarczane do mnie w ramach biblioteki, której używam:// file enums.h #include <string> class Enum1 { public: enum EnumerationItem { BEARS1, BEARS2, BEARS3 }; static std::string toString(EnumerationItem e) { // code for converting e to its string representation, // omitted for brevity } }; class Enum2 { public: enum EnumerationItem { TIGERS1, TIGERS2, TIGERS3 }; static std::string toString(EnumerationItem e) { // code for converting e to its string representation, // omitted for brevity } };
dodanie tej linii tylko po to, aby oddzielić następny plik do innego bloku kodu:
// file TemplateExample.h #include <string> template <typename T> class TemplateExample { public: TemplateExample(T t); virtual ~TemplateExample(); // this is the function I was most concerned about. Unlike @maxim1000's // answer where (s)he declared it outside the class with full template // parameters, I was able to keep mine declared in the class just like // this std::string toString(); private: T type_; }; template <typename T> TemplateExample<T>::TemplateExample(T t) : type_(t) { } template <typename T> TemplateExample<T>::~TemplateExample() { }
następny plik
// file TemplateExample.cpp #include <string> #include "enums.h" #include "TemplateExample.h" // for each enum type, I specify a different toString method, and the // correct one gets called when I call it on that type. template <> std::string TemplateExample<Enum1::EnumerationItem>::toString() { return Enum1::toString(type_); } template <> std::string TemplateExample<Enum2::EnumerationItem>::toString() { return Enum2::toString(type_); }
następny plik
// and finally, main.cpp #include <iostream> #include "TemplateExample.h" #include "enums.h" int main() { TemplateExample<Enum1::EnumerationItem> t1(Enum1::EnumerationItem::BEARS1); TemplateExample<Enum2::EnumerationItem> t2(Enum2::EnumerationItem::TIGERS3); std::cout << t1.toString() << std::endl; std::cout << t2.toString() << std::endl; return 0; }
i to daje:
Nie mam pojęcia, czy jest to idealne rozwiązanie mojego problemu, ale zadziałało. Teraz, bez względu na to, ile typów wyliczeń ostatecznie używam, wszystko, co muszę zrobić, to dodać kilka wierszy dla
toString
metody w pliku .cpp i mogę użyć metody już zdefiniowanej w bibliotekachtoString
bez jej samodzielnego wdrażania i bez rozszerzania każdej z nichenum
klasa, której chcę użyć.źródło