2010-09-11 7 views
5

Learning C++, kam auf Funktionsvorlagen. Das Kapitel erwähnte Template-Spezialisierung.C++ - Was ist der Zweck der Spezialisierung von Funktionsvorlagen? Wann man es benutzt?

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

  2. void foo(int);

Warum spezialisieren, wenn Sie die zweite verwenden kann? Ich dachte, Vorlagen sollten verallgemeinern. Was ist der Sinn der Spezialisierung einer Funktion für einen bestimmten Datentyp, wenn Sie einfach eine reguläre Funktion verwenden können?

Offensichtlich existiert Template-Spezialisierung aus einem Grund. Wann sollte es verwendet werden? Ich lese Sutters "Why not Specialize..." Artikel, aber ich brauche mehr von einer Laienversion, da ich gerade dieses Zeug lerne.

+0

vielleicht die Adresse: '& foo vs (void (...)) foo' oder spezialisierte Rückgabetyp? – Anycorn

+0

mögliche Duplikate von [Funktion Template Spezialisierung Wichtigkeit und Notwendigkeit] (http://stackoverflow.com/questions/2197141/function-template-specialization-importance-and-necessity) – jamesdlin

Antwort

11

Der Hauptunterschied besteht darin, dass Sie dem Compiler im ersten Fall eine Implementierung für den bestimmten Typ zur Verfügung stellen, während Sie im zweiten Fall eine nicht verwandte Funktion ohne Templaten bereitstellen. Wenn Sie den Compiler immer auf die Typen schließen lassen, werden vom Compiler über eine Vorlage nicht vorlagenbasierte Funktionen bevorzugt, und der Compiler ruft die freie Funktion anstelle der Vorlage auf, um eine nicht-gestützte Funktion bereitzustellen, die übereinstimmt Die Argumente haben in den meisten Fällen denselben Effekt von Spezialisierungen.

Auf der anderen Seite, wenn an jedem Ort Sie das Argument Vorlage an (anstelle der Compiler schließen zu lassen), dann wird es nur die generische Vorlage nennen und wahrscheinlich zu unerwarteten Ergebnissen führen:

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) 
} 

Wenn Sie hatte eine Spezialisierung für int der Vorlage zur Verfügung gestellt, würden die letzten zwei Aufrufe diese Spezialisierung und nicht die generische Vorlage aufrufen.

+1

Guter Punkt über die 'f ' Sache. –

+0

Es gibt mehrere weitere Unterschiede. Überladungen spielen nicht gut mit qualifizierten Namen, es sei denn, sie sind im selben Umfang deklariert. Die Erweiterung von Standardbibliotheksfunktionen erfordert Spezialisierung, nicht Überladung.Die Unbeständigkeit von ADL neigt dazu, mit mehr Überlastungen zuzunehmen. – Potatoswatter

+0

Obwohl, wenn Ihre Schnittstelle angibt, dass es eine Funktion (-template) 'f' gibt, die Sie aufrufen können, dürfen Sie sich nicht auf etwas wie' f 'verlassen, um zu arbeiten, und Sie brauchen es auch nicht, weil die Vorlage Argument wird trotzdem abgeleitet. Die Erweiterung der Standard-Bibliotheksfunktionalität kann durch ADL (wie mit 'std :: swap') schön gemacht werden, während es oft nicht möglich ist, indem man es auf den Namespace' std' spezialisiert (man kann es beispielsweise nicht für ein Templat-Argument spezialisieren) würde eine teilweise Spezialisierung erfordern). Die bereichsbasierte for-Schleife basiert vollständig auf ADL. –

1

Sie können Spezialisierung verwenden, wenn Sie für eine bestimmte Klasse wissen, dass die generische Methode effizient sein könnte.

Jetzt für Vektoren funktioniert mein Tausch, aber ist nicht sehr effecient. Aber ich weiß auch, dass std :: vector eine eigene swap() -Methode implementiert.

Bitte don; t vergleichen mit std :: swap, das ist viel komplexer und besser geschrieben. Dies ist nur ein Beispiel, um zu zeigen, dass eine generische Version von MySwap() funktioniert, aber möglicherweise nicht immer effizient ist. Als Ergebnis habe ich gezeigt, wie es mit einer sehr spezifischen Template-Spezialisierung effizienter gemacht werden kann.

Wir können natürlich auch Überladung verwenden, um denselben Effekt zu erzielen.

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

Also die Frage, warum Template-Spezialisierung verwenden (wenn man Überladung verwenden kann). Warum in der Tat. Eine Nicht-Template-Funktion wird immer über eine Template-Funktion ausgewählt. So werden Vorlagenregelungsregeln nicht einmal aufgerufen (was das Leben viel einfacher macht, da diese Regeln bizarr sind, wenn Sie kein Anwalt und kein Computerprogrammierer sind). Also lass mich eine Sekunde warten. Nein, ich kann mir keinen guten Grund vorstellen.

+0

Die Frage war, warum nicht überladen statt Spezialisierung zu verwenden? –

+0

Martin, ich denke, er fragte, warum die Spezialisierung und nicht eine nicht-Vorlage Überladung von MySwap(). –

4

Ich persönlich kann keinen Vorteil aus der Spezialisierung einer Funktionsvorlage sehen. Das Überladen durch eine andere Funktionsschablone oder eine Nicht-Schablonenfunktion ist wohl überlegen, da seine Handhabung intuitiver und insgesamt leistungsfähiger ist (effektiv durch Überladen der Schablone, haben Sie eine teilweise Spezialisierung der Schablone, obwohl sie technisch teilweise heißt Bestellung).

Herb Sutter hat einen Artikel Why not specialize function templates? geschrieben, in dem er die Spezialisierung von Funktionsvorlagen zugunsten der Überladung oder des Schreibens von ihnen rät, so dass sie nur an die statische Funktion einer Klassenvorlage weiterleiten und stattdessen die Klassenvorlage spezialisieren.

+0

Da Funktions-Template-Spezialisierungen nicht am Überladungsprozess teilnehmen, ist 'Funktions-Template-Spezialisierung 'ein Vorteil, weil es die Überladungsauflösung beschleunigt – Chubsdad

+0

@Chubsdad: Ich würde niemals versuchen, die Kompilierzeit zu optimieren. Entscheidungen sollten höher als die Zeit getroffen werden, die der Compiler benötigt, um den Code zu verarbeiten. –

+0

Sie sollten lieber versuchen, den Compiler selbst zu optimieren :) –

0

Ich finde es sehr wichtig. Sie können dies verwenden, als würden Sie eine virtuelle Methode verwenden. Virtuelle Methoden wären nicht sinnvoll, es sei denn, einige von ihnen wären spezialisiert. Ich habe es sehr oft verwendet, um zwischen einfachen Typen (int, short, float) und Objekten, Objektzeigern und Objektreferenzen zu unterscheiden. Ein Beispiel wäre serialize/unserialize-Methoden, die Objekte durch Aufruf der serialize/deserialize-Methode behandeln würden, während einfache Typen direkt in einen Stream geschrieben werden sollten.

+0

Eine virtuelle Methode kann keine Vorlage sein. – Potatoswatter

+0

@Potato: Ich denke, er zeichnete eine Parallele. Wenn Sie eine Funktion als virtuell deklarieren, aber keine abgeleiteten Klassen diese überladen, wird die virtuelle Art verschwendet. Es ist natürlich keine sehr gute Parallele. –

-1

Ein Fall für Template-Spezialisierung, die nicht mit Überladung möglich ist, ist für die Vorlage Meta-Programmierung. Das Folgende ist echter Code aus einer Bibliothek, die einige der Dienste zur Kompilierzeit bereitstellt.

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

Das sieht nicht richtig aus. 'stdout' ist ein Objekt (und es kann ein Makro sein, das fast alles ruiniert), also sollte es nicht mit dem Argumenttyp' classOs' übereinstimmen. Wenn überhaupt, brauchen Sie 'Vorlage < FILE * > Std :: ostream & get();' – Potatoswatter

+0

@Potatoswatter: seine interne (Namespace internal). Später wird es unter einem Eigennamen in einen öffentlichen (nicht globalen) Namensraum der Bibliothek gebracht. Vorlage ist auch gut. – Daniel

0

Mehrere Überlastungen auf dem gleichen Namen tun ähnlich Dinge. Spezialisierungen machen die genau dieselbe Sache, aber auf verschiedenen Arten. Überladungen haben denselben Namen, können jedoch in verschiedenen Bereichen definiert werden. Eine Vorlage wird nur in einem Bereich deklariert, und der Speicherort einer Spezialisierungsdeklaration ist nicht signifikant (obwohl sie im Bereich des umschließenden Namespace liegen muss).

Zum Beispiel, wenn Sie std::swap erweitern Art zu unterstützen, müssen Sie durch Spezialisierung tun so, weil die Funktion gestattet std::swap, nicht einfach swap und die Funktionen in <algorithm> würden ganz richtig sein, speziell zu nennen als ::std::swap(a, b);. Gleichermaßen für jeden Namen, der in Namespaces Alias ​​enthalten könnte: Das Aufrufen einer Funktion kann "härter" werden, sobald Sie den Namen qualifiziert haben.

Das Scoping-Problem wird weiter durch argument-dependent lookup verwirrt. Oft kann eine Überladung gefunden werden, da sie in der Nähe des Typs eines ihrer Argumente definiert ist. (Zum Beispiel als statische Member-Funktion.) Dies ist völlig anders als die Template-Spezialisierung, die einfach durch Aufsuchen des Vorlagennamens und Nachschlagen der expliziten Spezialisierung nach der Auswahl der Vorlage als Ziel erfolgt des Anrufs.

Die Regeln von ADL sind der verwirrendste Teil des Standards, deshalb bevorzuge ich eine explizite Spezialisierung auf das Prinzip der Vermeidung von Vertrauen darauf.

+0

Nun, formal Überladung passiert wirklich nur in einem Bereich. Wenn sich zwei Funktionsdeklarationen in verschiedenen Bereichen befinden, überladen sie nicht jeden anderen. Aber der eigentliche Punkt, den ich machen möchte, ist - stellen Sie sich vor, Sie haben eine 'Schablone Klasse Container {...};' - wie möchten Sie 'std :: swap' spezialisieren, um an diesem Container zu arbeiten? Es ist nicht möglich, und Sie werden wahrscheinlich das mit ADL tun. Aus diesem Grund werden generische Funktionen nicht nur 'std :: swap 'aufrufen, da dies eine völlig lahme Implementierungsqualität wäre. –

+1

Ich denke, das allgemeine Muster besteht darin, '{using std :: swap; tauschen (a, b); } 'das findet beide Deklarationen von ADL und Spezialisierungen von' std :: swap'. Ich finde es leichter zu überladen. Jeder sollte es tun, wie er es für richtig hält :) –

+0

@Johannes: Sie haben eine praktikable Lösung, aber es ist nicht, was der Standard angibt. Es geht darum, 'swap' für eine Klasse zu implementieren, so dass sie von' 'gefunden wird. (Ja, nur der "beste" Bereich wird verwendet, um eine Übereinstimmung zu finden ... aber auch eine Quelle der Frustration! Es gibt dort eine klare Prioritätsinversion, da der Benutzer Scoping-Details wahrscheinlich nicht als den wichtigsten Faktor sehen wird.) – Potatoswatter