Specjalizacja szablonowa pojedynczej metody z klasy opartej na szablonie

92

Zawsze biorąc pod uwagę, że następujący nagłówek, zawierający moją klasę z szablonu, jest zawarty w co najmniej dwóch .CPPplikach, 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!

Chuim
źródło
4
Dlaczego chcesz usunąć inline? Czy wydaje Ci się to nieprzyjemne ze względów estetycznych? Myślisz, że zmienia to znaczenie twojego kodu?
Martin York,
1
Bo gdyby ta metoda była „długa” i używana w wielu miejscach, kod binarny byłby kopiowany wszędzie, prawda? Próbowałem to wyjaśnić w pytaniu, ale wydaje mi się, że nie było to jasne ... :)
Chuim
@Martin: A co, jeśli implementacja wymaga wielu innych kodów, które następnie muszą być zawarte w tym nagłówku zamiast w pliku cpp?
sbi

Odpowiedzi:

72

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

maksymalny1000
źródło
Próbowałem wcześniej czegoś przynajmniej podobnego, ale otrzymałem inne błędy, ale teraz, gdy wspomniałeś, musiałem zapomnieć o usunięciu inlinepodczas kopiowania / wklejania. Tak to zadziałało!
Chuim,
To samo dotyczy funkcji wolnych od szablonów (w przeciwieństwie do metod klasowych). Otrzymałem ten sam błąd konsolidatora dla mojej specjalizacji funkcji. Przeniosłem treść specjalizacji funkcji do pliku .cpp i zostawiłem deklarację specjalizacji w nagłówku i wszystko działało. Dzięki!
aldo
Właśnie natknąłem się na ten problem, a powyższe rozwiązało go za mnie. Dodatkowo musisz zadbać o to, gdzie kompilator rozszerza kod szablonu. Jeśli zostanie to zrobione dwukrotnie, kompilator narzeka na wiele definicji.
Diederik,
4

Musisz przenieść definicję specjalizacji do pliku CPP. Specjalizacja funkcji składowej klasy szablonu jest dozwolona, ​​nawet jeśli funkcja nie jest zadeklarowana jako szablon.

BostonLogan
źródło
3

Nie ma powodu, aby usuwać wbudowane słowo kluczowe.
W żaden sposób nie zmienia to znaczenia kodu.

Martin York
źródło
Skopiowano z komentarza do pytania: Ponieważ gdyby ta metoda była „długa” i używana w wielu miejscach, kod binarny byłby kopiowany wszędzie, prawda? Próbowałem to wyjaśnić w pytaniu, ale wydaje mi się, że nie było to jasne ... :)
Chuim
1
Nie. Linker usuwa wszelkie dodatkowe kopie. Tak więc w aplikacji lub bibliotece będziesz mieć tylko jedną instancję metody.
Martin York,
3
Jeśli inlinesł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")
Yakk - Adam Nevraumont
2

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

Istnieje kilka sposobów oznaczenia, że ​​funkcja jest wbudowana, z których niektóre obejmują słowo kluczowe inline, a inne nie. Bez względu na to, jak oznaczysz funkcję jako inline, jest to żądanie, które kompilator może zignorować: kompilator może rozwinąć w wierszu niektóre, wszystkie lub żadne miejsca, w których wywołujesz funkcję oznaczoną jako inline. (Nie zniechęcaj się, jeśli wydaje się to beznadziejnie niejasne. Elastyczność powyższego jest w rzeczywistości ogromną zaletą: pozwala kompilatorowi traktować duże funkcje inaczej niż małe, a ponadto pozwala kompilatorowi generować kod, który jest łatwy do debugowania, jeśli wybierzesz odpowiednie opcje kompilatora).

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

Triskeldeian
źródło
1

Chciałbym dodać, że nadal istnieje dobry powód, aby pozostawić inlinetam słowo kluczowe, jeśli zamierzasz pozostawić również specjalizację w pliku nagłówkowym.

„Intuicyjnie, gdy w pełni specjalizujesz się w czymś, nie zależy to już od parametru szablonu - więc jeśli nie wprowadzisz specjalizacji w tekście, musisz umieścić ją w pliku .cpp zamiast .h lub w końcu naruszysz zasada jednej definicji… ”

Źródła: https://stackoverflow.com/a/4445772/1294184

Jordania
źródło
0

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 enums, ale wszystkie klasy dziedziczą po wspólnym (abstrakcyjnym) rodzicu i zapewniają różne funkcje użytkowe, takie jak przeciążanie operatora i static toString(enum type)funkcja. Każdy status enumróżni się od siebie i nie ma ze sobą związku. Na przykład, jeden enumma pola NORMAL, DEGRADED, INOPERABLE, inny ma AVAILBLE, PENDING, MISSINGitd. Moje oprogramowanie zarządza różnymi rodzajami statusów różnych komponentów. Doszło do tego, że chciałem do tego wykorzystać te toStringfunkcjeenumklasy, 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ć templateklasę, w której typenamebyłby taki konkretny status, na którym enummi zależy. Prawdopodobnie można dyskutować na temat tej decyzji, ale czułem, że było to o wiele mniej pracy niż rozszerzenie każdej enumklasy 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ągu enum. Ponieważ wszystkie enumbyły zupełnie niepowiązane, każdy z nich miał swoje własnetoStringfunkcje, 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 enums. W rzeczywistości każda enumklasa 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:

BEARS1
TIGERS3

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 toStringmetody w pliku .cpp i mogę użyć metody już zdefiniowanej w bibliotekach toStringbez jej samodzielnego wdrażania i bez rozszerzania każdej z nich enumklasa, której chcę użyć.

yano
źródło