2010-09-11 10 views
5

Nauka C++, natrafiła na szablony funkcji. W rozdziale wspomniano o specjalizacji szablonu.C++ - Jaki jest cel specjalizacji szablonów funkcji? Kiedy go używać?

  1. template <> void foo<int>(int);

  2. void foo(int);

Dlaczego specjalizują kiedy można użyć drugiego? Myślałem, że szablony mają generalizować. Jaki jest sens specjalizacji funkcji dla określonego typu danych, kiedy możesz po prostu użyć zwykłej funkcji?

Oczywiście specjalizacja szablonów istnieje z jakiegoś powodu. Kiedy należy go użyć? Przeczytałem artykuł Suttera o numerze "Why not Specialize...", ale potrzebuję więcej wersji laika, ponieważ dopiero się tego uczę.

+0

być może biorąc adres: '& foo vs (void (...)) foo' lub specjalizujący się typ zwrotu? – Anycorn

+0

możliwy duplikat [Znaczenie i konieczność specjalizacji szablonu funkcji] (http://stackoverflow.com/questions/2197141/function-template-specialization-importance-and-necessity) – jamesdlin

Odpowiedz

11

Główna różnica polega na tym, że w pierwszym przypadku dostarczasz kompilatorowi implementację dla określonego typu, podczas gdy w drugim dostarczasz niepowiązaną, niesformatowaną funkcję.

Jeśli zawsze pozwalasz kompilatorowi wywnioskować typy, kompilator zamiast szablonu wybierze funkcje niesformowane, a kompilator wywoła funkcję wolną zamiast szablonu, zapewniając w ten sposób niesformatowaną funkcję, która pasuje do w większości przypadków argumenty będą miały ten sam skutek specjalizacji.

Z drugiej strony, jeśli w dowolnym miejscu podać argument szablonu (zamiast pozwalając wnioskować kompilatora), to będzie po prostu zadzwonić do szablonu rodzajowy i prawdopodobnie spowodować nieoczekiwane rezultaty:

template <typename T> void f(T) { 
    std::cout << "generic" << std::endl; 
} 
void f(int) { 
    std::cout << "f(int)" << std::endl; 
} 
int main() { 
    int x = 0; 
    double d = 0.0; 
    f(d); // generic 
    f(x); // f(int) 
    f<int>(x); // generic !! maybe not what you want 
    f<int>(d); // generic (same as above) 
} 

Jeśli pod warunkiem specjalizacji dla szablonu int, dwa ostatnie połączenia będą wywoływać tę specjalizację, a nie ogólny szablon.

+1

Dobra rzecz o rzeczy 'f '. –

+0

Istnieje kilka innych różnic. Przeciążenia nie działają dobrze z kwalifikowanymi nazwami, chyba że zadeklarowano wszystkie w tym samym zakresie. Rozszerzanie standardowych funkcji bibliotecznych wymaga specjalizacji, a nie przeciążania.Zjawisko ADL ma tendencję do wzrostu wraz z większą ilością przeciążeń. – Potatoswatter

+0

Chociaż twój interfejs stwierdza, że ​​istnieje funkcja (-template) 'f', którą możesz wywołać, to nie możesz polegać na czymś takim jak' f 'do pracy i nie potrzebujesz tego albo dlatego, że szablon argument jest i tak wyprowadzony. Rozszerzanie standardowej funkcjonalności biblioteki może być ładnie wykonane przez ADL (np. Z 'std :: swap'), podczas gdy często nie można tego zrobić, specjalizując się w przestrzeni nazw' std' (nie możesz wyspecyfikować go na przykład na przykładowym szablonie - to wymagałoby częściowej specjalizacji). Pętla oparta na zasięgu jest w całości oparta na ADL. –

1

Możesz używać specjalizacji, gdy wiesz, że dla konkretnej klasy skuteczna może być metoda ogólna.

template<typename T> 
void MySwap(T& lhs, T& rhs) 
{ 
    T tmp(lhs); 
    lhs = rhs; 
    rhs = tmp; 
} 

Teraz dla wektorów mój zamiana będzie działać, ale nie jest zbyt skuteczny. Ale wiem też, że std :: vector implementuje własną metodę swap().

template<> 
void MySwap(std::vector<int>& lhs,std::vector<int>& rhs) 
{ 
    lhs.swap(rhs); 
} 

Proszę nie porównywać do std :: swap, które jest dużo bardziej złożone i lepiej napisane. Jest to tylko przykład pokazujący, że standardowa wersja MySwap() będzie działać, ale nie zawsze może być wydajna. W rezultacie pokazałem, jak można go usprawnić dzięki specjalnej specjalizacji szablonu.

Możemy oczywiście użyć przeciążenia, aby uzyskać ten sam efekt.

void MySwap(std::vector<int>& lhs,std::vector<int>& rhs) 
{ 
    lhs.swap(rhs); 
} 

Więc pytanie, dlaczego warto korzystać ze specjalizacji szablonów (jeśli można użyć przeciążenia). Dlaczego rzeczywiście. Funkcja bez szablonu będzie zawsze wybierana przez funkcję szablonu. Więc zasady specjalizacji szablonów nie są nawet wywoływane (co sprawia, że ​​życie jest o wiele prostsze, ponieważ reguły te są dziwne, jeśli nie jesteś prawnikiem, a także programistą komputerowym). Więc pozwól mi coś na sekundę. Nie można wymyślić dobrego powodu.

+0

Pytanie brzmiało, dlaczego nie używać przeciążenia zamiast specjalizacji? –

+0

Martin, myślę, że pytał, dlaczego zapewnić specjalizację, a nie szablonowe przeciążenie MySwap(). –

4

Osobiście nie widzę korzyści ze specjalizacji szablonu funkcji. Przeciążenie go przez inny szablon funkcji lub funkcję inną niż szablonowa jest prawdopodobnie lepsze, ponieważ jego obsługa jest bardziej intuicyjna i jest ogólnie silniejsza (skutecznie przez przeciążenie szablonu, masz częściową specjalizację szablonu, nawet jeśli pod względem technicznym nazywa się to częściowym zamówienie).

Herb Sutter napisał artykuł Why not specialize function templates?, w którym zniechęca szablony funkcji specjalnych na rzecz ich przeciążenia lub zapisania w taki sposób, aby po prostu przekazywały do ​​statycznej funkcji szablonu klasy i specjalizowały się w szablonie klasy.

+0

Ponieważ specyfikacje szablonów funkcji nie biorą udziału w przeciążaniu procesu, "specjalizacja szablonów funkcji" jest korzystna, ponieważ przyśpiesza rozdzielczość przeciążania. – Chubsdad

+0

@Chubsdad: Nigdy nie próbowałbym optymalizować czasu kompilacji. Decyzje powinny być podejmowane z wyższymi podstawami niż czas, jaki zajmuje kompilator na przetworzenie kodu. –

+0

Powinieneś raczej spróbować zoptymalizować sam kompilator :) –

0

Uważam, że to bardzo ważne. Możesz użyć tego tak, jakbyś użył wirtualnej metody. W wirtualnych metodach nie byłoby sensu, chyba że niektóre z nich były wyspecjalizowane. Używałem go często do rozróżniania prostych typów (int, short, float) i obiektów, wskaźników obiektów i odniesień do obiektów. Przykładem byłoby serializowanie/odserializowanie metod, które obsługiwałyby obiekty przez wywoływanie metody serializacji/odserializowania obiektów, podczas gdy typy proste powinny być zapisywane bezpośrednio w strumieniu.

+0

Metoda wirtualna nie może być szablonem. – Potatoswatter

+0

@Potato: Myślę, że rysował równolegle. Jeśli zadeklarujesz funkcję wirtualną, ale żadne klasy pochodne jej nie przeciążą, wirtualny rodzaj zostanie zmarnowany. Z pewnością nie jest to bardzo dobra równoległość. –

-1

Jeden przypadek specjalizacji szablonu, który nie jest możliwy w przypadku przeciążenia, dotyczy meta-programowania szablonów. Poniżej znajduje się prawdziwy kod z biblioteki, która udostępnia niektóre usługi w czasie kompilacji.

namespace internal{namespace os{ 
    template <class Os> std::ostream& get(); 

    struct stdout{}; 
    struct stderr{}; 

    template <> inline std::ostream& get<stdout>() { return std::cout; } 
    template <> inline std::ostream& get<stderr>() { return std::cerr; } 
}} 

// define a specialization for os::get() 
#define DEFINE_FILE(ofs_name,filename)\ 
    namespace internal{namespace os{      \ 
     struct ofs_name{         \ 
      std::ofstream ofs;        \ 
      ofs_name(){ ofs.open(filename);}      \ 
      ~ofs_name(){ ofs.close(); delete this; }     \ 
     };           \ 
     template <> inline std::ostream& get<ofs_name>(){ return (new ofs_name())->ofs; } \ 
    }}            \ 
    using internal::os::ofs_name; 
+0

To nie wygląda dobrze. 'stdout' jest obiektem (i może być makrem, który prawie wszystko rujnuje), więc nie powinien odpowiadać argumentowi' class Os'. Jeśli cokolwiek, potrzebujesz 'szablonu < FILE * > std :: ostream & get();' – Potatoswatter

+0

@Potatoswatter: jego wewnętrzny (wewnętrzna przestrzeń nazw). Później zostaje wprowadzony do publicznej (nie globalnej) przestrzeni nazw biblioteki pod własną nazwą. szablon jest również dobry. – Daniel

0

Wiele przeciążenia o tej samej nazwie zrobić podobnych rzeczy. Specjalizacje robią dokładnie to samo, co, ale na różnych typach. Przeciążenia mają tę samą nazwę, ale mogą być zdefiniowane w różnych zakresach. Szablon jest zadeklarowany tylko w jednym zasięgu, a położenie deklaracji specjalizacyjnej jest nieznaczne (chociaż musi znajdować się w zasięgu otaczającej przestrzeni nazw).

Na przykład, jeśli przedłużyć std::swap wspierać swój typ, trzeba zrobić poprzez specjalizację, ponieważ funkcja nosi nazwę std::swap, a nie po prostu swap i funkcje w <algorithm> byłoby słusznie konkretnie nazwać jako ::std::swap(a, b);. Podobnie w przypadku dowolnej nazwy, która może być aliasowana w przestrzeniach nazw: wywoływanie funkcji może stać się "trudniejsze" po zakwalifikowaniu nazwy.

Problem z ustalaniem zakresu jest jeszcze bardziej niejasny, ponieważ argument-dependent lookup. Często można znaleźć przeciążenie, ponieważ jest ono zdefiniowane w pobliżu typu jednego z jego argumentów. (Na przykład jako statyczna funkcja składowa.) Jest to całkowicie odmienne od tego, jak można znaleźć specjalizację szablonu, czyli po prostu po wyszukaniu nazwy szablonu, a następnie przejrzeniu jawnej specjalizacji po wybraniu szablonu jako celu połączenia.

Zasady ADL są najbardziej kłopotliwą częścią normy, więc wolę wyraźną specjalizację w zakresie unikania polegania na niej.

+0

Cóż, formalnie przeciążanie naprawdę dzieje się tylko w jednym zakresie. Jeśli dwie deklaracje funkcji znajdują się w różnych zakresach, nie przeciążają każdego z nich. Ale rzeczywisty punkt, który chcę zrobić, to - wyobrazić sobie, że masz kontener # template {...}; '- jak chcesz specjalizować' std :: swap' do pracy z tym kontenerem? Nie jest to możliwe i prawdopodobnie będziesz to robić za pomocą ADL. Z tego powodu funkcje generyczne nie będą nazywać po prostu 'std :: swap', ponieważ będzie to całkowicie kiepska jakość implementacji. –

+1

Myślę, że ogólny wzór polega na napisaniu '{using std :: swap; zamiana (a, b); } ', który znajduje obie deklaracje przez ADL i specjalizacje' std :: swap'. Łatwiej jest przeciążać. Każdy powinien to zrobić, jak mu się podoba :) –

+0

@Johannes: Masz praktyczne rozwiązanie, ale nie jest to zgodne ze standardem. Chodzi o to, aby zaimplementować 'swap' dla klasy takiej, że zostanie znalezione przez' '. (Tak, tylko "najlepszy" zakres jest używany do znalezienia dopasowania ... ale także może być źródłem frustracji! Istnieje wyraźna inwersja priorytetów, ponieważ użytkownik prawdopodobnie nie zobaczy szczegółowych danych dotyczących zakresu jako najważniejszego czynnika.) – Potatoswatter

Powiązane problemy