Vorlagenspezialisierung einer einzelnen Methode aus einer Vorlagenklasse

92

In Anbetracht der Tatsache, dass der folgende Header, der meine Vorlagenklasse enthält, in mindestens zwei .CPPDateien enthalten ist, wird dieser Code korrekt 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 Linkerfehler zu vermeiden (in VS2008 ist LNK2005), da die Methode mehr als einmal definiert wurde. Ich verstehe das, weil AFAIK eine vollständige Template-Spezialisierung mit einer einfachen Methodendefinition identisch ist.

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

Vielen Dank!

Chuim
quelle
4
Warum möchten Sie die Inline entfernen? Finden Sie es ästhetisch unangenehm? Denken Sie, dass dies die Bedeutung Ihres Codes ändert?
Martin York
1
Denn wenn diese Methode "lang" wäre und an vielen Stellen verwendet würde, würde ich ihren Binärcode überall kopieren lassen, oder? Ich habe versucht, dies in der Frage zu erklären, aber ich denke, es war nicht klar ... :)
Chuim
@Martin: Was ist, wenn die Implementierung viel anderen Code benötigt, der dann in diesem Header anstelle der CPP-Datei enthalten sein muss?
sbi

Antworten:

71

Wie bei 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üge die Implementierung in eine deiner 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 dachte, diese Lösung wird nicht funktionieren :)). Auf VC ++ 2005 überprüft

maxim1000
quelle
Ich habe etwas Ähnliches versucht, aber ich habe andere Fehler erhalten, aber jetzt, wo Sie es erwähnt haben, muss ich vergessen haben, das inlinebeim Kopieren / Einfügen zu entfernen . So hat es funktioniert!
Chuim
Gleiches gilt für vorlagenfreie Funktionen (im Gegensatz zu Klassenmethoden). Ich habe den gleichen Linkerfehler für meine Funktionsspezialisierung erhalten. Ich habe den Hauptteil der Funktionsspezialisierung in eine CPP-Datei verschoben und die Deklaration der Spezialisierung im Header belassen, und alles hat funktioniert. Vielen Dank!
Aldo
Ich bin gerade auf dieses Problem gestoßen, und das oben genannte hat es für mich gelöst. Außerdem müssen Sie darauf achten, wo der Compiler den Vorlagencode erweitert . Wenn dies zweimal durchgeführt wird, beschwert sich der Compiler über mehrere Definitionen.
Diederik
4

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

BostonLogan
quelle
3

Es gibt keinen Grund, das Schlüsselwort inline zu entfernen.
Die Bedeutung des Codes wird dadurch ohnehin nicht geändert.

Martin York
quelle
Aus dem Fragekommentar kopiert: Denn wenn diese Methode "lang" wäre und an vielen Stellen verwendet würde, würde ich ihren Binärcode überall kopieren lassen, oder? Ich habe versucht, dies in der Frage zu erklären, aber ich denke, es war nicht klar ... :)
Chuim
1
Nein. Der Linker entfernt alle zusätzlichen Kopien. Innerhalb einer Anwendung oder Bibliothek hätten Sie also nur eine Instanz der Methode.
Martin York
3
Wenn das inlineSchlüsselwort dazu führt, dass die Funktion tatsächlich inline ist (der Standard besagt, dass der Compiler dies als Hinweis nehmen sollte), können diese zusätzlichen Kopien nicht entfernt werden. Es ist jedoch nur ein Hinweis auf Inline (sein primärer Effekt ist zu sagen "Generiere keine Fehler bei Verbindungskollisionen auf eine bestimmte Weise")
Yakk - Adam Nevraumont
2

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

In Ihrem Kommentar scheinen Sie jedoch zu glauben, dass das Inline-Schlüsselwort bedeutet, dass die Funktion mit all seinen Inhalten immer inline wird, aber AFAIK, das tatsächlich sehr stark von Ihrer Compiler-Optimierung abhängt.

Zitieren aus den C ++ FAQ

Es gibt verschiedene Möglichkeiten, um anzugeben, dass eine Funktion inline ist, von denen einige das Inline-Schlüsselwort enthalten, andere nicht. Unabhängig davon, wie Sie eine Funktion als Inline festlegen, darf der Compiler diese ignorieren: Der Compiler kann einige, alle oder keine der Stellen, an denen Sie eine als Inline bezeichnete Funktion aufrufen, inline erweitern. (Lassen Sie sich nicht entmutigen, wenn dies hoffnungslos vage erscheint. Die Flexibilität der oben genannten Funktionen ist tatsächlich ein großer Vorteil: Der Compiler kann große Funktionen anders behandeln als kleine, und der Compiler kann Code generieren, der bei Auswahl einfach zu debuggen ist die richtigen Compiler-Optionen.)

Wenn Sie also nicht wissen, dass diese Funktion Ihre ausführbare Datei tatsächlich aufbläht, oder wenn Sie sie aus anderen Gründen aus dem Header der Vorlagendefinition entfernen möchten, können Sie sie tatsächlich dort belassen, wo sie ohne Schaden ist

Triskeldeian
quelle
1

Ich möchte hinzufügen, dass es immer noch einen guten Grund gibt, das inlineSchlüsselwort dort zu belassen, wenn Sie beabsichtigen, auch die Spezialisierung in der Header-Datei zu belassen.

"Wenn Sie etwas vollständig spezialisieren, hängt es intuitiv nicht mehr von einem Vorlagenparameter ab. Wenn Sie die Spezialisierung nicht inline durchführen, müssen Sie sie in eine CPP-Datei anstatt in eine .h-Datei einfügen, oder Sie verletzen sie die eine Definitionsregel ... "

Referenz: https://stackoverflow.com/a/4445772/1294184

Jordanien
quelle
0

Dies ist ein kleines OT, aber ich dachte, ich würde es hier lassen, falls es jemand anderem hilft. Ich habe über die Spezialisierung von Vorlagen gegoogelt, die mich hierher geführt hat, und obwohl die Antwort von @ maxim1000 richtig ist und mir letztendlich geholfen hat, meine Probleme herauszufinden, habe ich nicht gedacht, dass es sehr klar ist.

Meine Situation ist etwas anders (aber ähnlich genug, um diese Antwort zu hinterlassen, denke ich) als die der OPs. Grundsätzlich verwende ich eine Drittanbieter-Bibliothek mit allen Arten von Klassen, die "Statustypen" definieren. Das Herzstück dieser Typen sind einfach enums, aber die Klassen erben alle von einem gemeinsamen (abstrakten) übergeordneten Element und bieten verschiedene Dienstprogrammfunktionen, wie z. B. das Überladen von Operatoren und eine static toString(enum type)Funktion. Jeder Status enumunterscheidet sich voneinander und ist nicht miteinander verbunden. Zum Beispiel hat einer enumdie Felder NORMAL, DEGRADED, INOPERABLE, ein anderer hat AVAILBLE, PENDING, MISSINGusw. Meine Software ist verantwortlich für die Verwaltung verschiedener Arten von Status für verschiedene Komponenten. Es entstand, dass ich die toStringFunktionen für diese nutzen wollteenumKlassen, aber da sie abstrakt sind, konnte ich sie nicht direkt instanziieren. Ich hätte jede Klasse, die ich verwenden wollte, erweitern können, aber letztendlich habe ich beschlossen, eine templateKlasse zu erstellen , in der typenamees sich um einen konkreten Status handelt, der . Da alle s völlig unabhängig waren, hatten sie jeweils ihre eigenenenumIch habe mich darum gekümmert. Wahrscheinlich kann eine Debatte über diese Entscheidung geführt werden, aber ich hatte das Gefühl, dass dies viel weniger Arbeit war, als jede abstrakte enumKlasse um eine eigene zu erweitern und die abstrakten Funktionen zu implementieren. Und natürlich wollte ich in meinem Code nur .toString(enum type)die Zeichenfolgendarstellung dieser Funktionen aufrufen und ausdrucken können, die (nach einigen Recherchen) mithilfe der Vorlagenspezialisierung aufgerufen werden mussten. Das hat mich hierher geführt. Unten ist eine MCVE von dem, was ich tun musste, damit dies richtig funktioniert. Und tatsächlich war meine Lösung etwas anders als die von @ maxim1000.enumenumtoString

Dies ist eine (stark vereinfachte) Header-Datei für die enums. In Wirklichkeit wurde jede enumKlasse 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
  }
};

Hinzufügen dieser Zeile, um die nächste Datei in einen anderen Codeblock zu unterteilen:

// 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 bei mir funktioniert. Unabhängig davon, wie viele Aufzählungstypen ich letztendlich verwende, muss ich nur ein paar Zeilen für die toStringMethode in die CPP-Datei einfügen, und ich kann die bereits definierte toStringMethode der Bibliothek verwenden, ohne sie selbst zu implementieren und ohne sie zu erweitern enumKlasse, die ich verwenden möchte.

Yano
quelle