it-swarm.com.de

Template-Spezialisierung einer einzelnen Methode aus einer Templated-Klasse

Immer unter Berücksichtigung der Tatsache, dass der folgende Header, der meine Klasse mit Vorlagen enthält, in mindestens zwei .CPP-Dateien enthalten ist, wird dieser Code richtig kompiliert:

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
}

Beachten Sie jedoch die Inline in der Spezialisierungsmethode. Es ist erforderlich, einen Linker-Fehler (in VS2008 ist LNK2005) zu vermeiden, da die Methode mehrmals definiert wird. Ich verstehe das, weil AFAIK eine vollständige Vorlagenspezialisierung einer einfachen Methodendefinition gleicht.

Wie entferne ich das inline? Der Code sollte nicht bei jeder Verwendung dupliziert werden. Ich habe Google gesucht, einige Fragen hier in SO gelesen und viele der vorgeschlagenen Lösungen ausprobiert, aber keine erfolgreich erstellt (zumindest nicht in VS 2008).

Vielen Dank!

75
Chuim

Wie bei den einfachen Funktionen können Sie Deklaration und Implementierung verwenden. __ Geben Sie Ihre Header-Deklaration ein:

template <>
void TClass<int>::doSomething(std::vector<int> * v);

und fügen Sie die Implementierung in eine Ihrer cpp-Dateien ein:

template <>
void TClass<int>::doSomething(std::vector<int> * v) {
 // Do somtehing with a vector of int's
}

Vergessen Sie nicht, Inline zu entfernen (ich habe vergessen und gedacht, dass diese Lösung nicht funktionieren wird :)) . Auf VC++ 2005 geprüft

61
maxim1000

Sie müssen die Spezialisierungsdefinition in die CPP-Datei verschieben. Die Spezialisierung der Member-Funktion der Vorlagenklasse ist zulässig, auch wenn die Funktion nicht als Vorlage deklariert ist.

4
BostonLogan

Es gibt keinen Grund, das Keyword inline zu entfernen.
.__ Die Bedeutung des Codes wird dadurch nicht geändert.

1
Martin York

Wenn Sie die Inline aus irgendeinem Grund entfernen möchten, ist die Lösung von maxim1000 vollkommen gültig.

In Ihrem Kommentar scheint es jedoch so, als würden Sie glauben, dass das Inline-Schlüsselwort bedeutet, dass die Funktion mit all seinen Inhalten immer inliniert wird. AFAIK ist jedoch in hohem Maße von Ihrer Compiler-Optimierung abhängig.

Zitieren aus dem C++ FAQ

Es gibt mehrere Möglichkeiten, um anzugeben, dass eine Funktion Inline ist, einige davon die das Inline-Schlüsselwort betreffen, andere nicht. Egal wie du eine Funktion als Inline festlegen, ist dies eine Anforderung, dass der Compiler .__ ist. erlaubt zu ignorieren: Der Compiler kann einige, alle oder keine .__ erweitern. der Orte, an denen Sie eine als inline bezeichnete Funktion aufrufen. (Lassen Sie sich nicht entmutigen, wenn das hoffnungslos vage erscheint. Die Flexibilität des oben genannten .__ ist tatsächlich ein großer Vorteil: Er lässt den Compiler große -Funktionen anders behandeln als kleine und den Compiler Code generieren, der leicht zu debuggen ist, wenn Sie die richtigen Compileroptionen auswählen.

Wenn Sie also nicht wissen, dass diese Funktion Ihre ausführbare Datei tatsächlich aufblähen kann, oder wenn Sie sie aus anderen Gründen aus dem Kopf der Vorlagendefinitionsdefinition entfernen möchten, können Sie sie tatsächlich an ihrem Ort belassen, ohne Schaden zu nehmen

1
Triskeldeian

Dies ist ein wenig OT, aber ich dachte, ich würde es hier lassen, falls es jemand anderem hilft. Ich habe über Schablonenspezialisierung gegoogelt, die mich hierher geführt hat, und während die Antwort von @ maxim1000 richtig ist und mir letztendlich geholfen hat, meine Probleme zu erkennen, war ich nicht der Meinung, dass dies völlig klar war.

Meine Situation ist etwas anders (aber ähnlich genug, um diese Antwort zu hinterlassen, denke ich) als bei den OPs. Im Grunde verwende ich eine Fremdanbieter-Bibliothek mit allen Arten von Klassen, die "Statustypen" definieren. Das Herzstück dieser Typen sind einfach enums, aber alle Klassen erben von einem gemeinsamen (abstrakten) übergeordneten Element und stellen verschiedene Dienstfunktionen bereit, z. B. das Überladen von Operatoren und eine static toString(enum type)-Funktion. Jeder Status enum unterscheidet sich voneinander und ist unabhängig. Beispielsweise hat eine enum die Felder NORMAL, DEGRADED, INOPERABLE, eine andere hat AVAILBLE, PENDING, MISSING, usw. Meine Software ist für die Verwaltung verschiedener Statusarten für verschiedene Komponenten verantwortlich. Ich wollte die toString-Funktionen für diese enum-Klassen verwenden, aber da sie abstrakt sind, konnte ich sie nicht direkt instanziieren. Ich hätte jede Klasse erweitern können, die ich verwenden wollte, aber letztendlich entschied ich mich, eine template-Klasse zu erstellen, wobei typename der konkrete Status enum sein würde, den ich interessiert habe. Wahrscheinlich gibt es einige Debatten über diese Entscheidung, aber ich hatte das Gefühl, dass dies viel weniger Arbeit war, als jede abstrakte enum-Klasse um eine eigene benutzerdefinierte Klasse zu erweitern und die abstrakten Funktionen zu implementieren. Und natürlich wollte ich in meinem Code nur .toString(enum type) aufrufen und die Zeichenfolgendarstellung von enum ausdrucken lassen. Da alle enums völlig unabhängig voneinander waren, hatten sie jeweils ihre eigenen toString-Funktionen, die (nach einiger Recherche, die ich gelernt hatte) mit Template-Spezialisierung aufgerufen werden mussten. Das hat mich hierher geführt. Unten ist ein MCVE von dem, was ich tun musste, um diese Funktion korrekt auszuführen. Und tatsächlich war meine Lösung etwas anders als @ maxim1000.

Dies ist eine (stark vereinfachte) Header-Datei für enums. In Wirklichkeit wurde jede enum-Klasse in einer eigenen Datei definiert. Diese Datei stellt die Header-Dateien dar, die mir als Teil der von mir verwendeten Bibliothek zur Verfügung gestellt werden:

// 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
  }
};

fügen Sie diese Zeile hinzu, um die nächste Datei in einen anderen Codeblock zu trennen:

// 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()
{

}

nächste Datei

// 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_);
}

nächste Datei

// 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;
}

und dies gibt aus:

BEARS1
TIGERS3

Keine Ahnung, ob dies die ideale Lösung ist, um mein Problem zu lösen, aber es hat für mich funktioniert. Unabhängig davon, wie viele Aufzählungstypen ich letztendlich verwende, muss ich nur noch ein paar Zeilen für die toString-Methode in der .cpp-Datei hinzufügen, und ich kann die bereits definierte Bibliotheken toString-Methode verwenden, ohne sie selbst und ohne zu implementieren Erweiterung jeder enum-Klasse, die ich verwenden möchte.

0
yano