Spécialisation de modèle d'une seule méthode à partir d'une classe basée sur un modèle

92

En considérant toujours que l'en-tête suivant, contenant ma classe basée sur un modèle, est inclus dans au moins deux .CPPfichiers, ce code se compile correctement:

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
}

Mais notez l'inline dans la méthode de spécialisation. Il est nécessaire d'éviter une erreur de l'éditeur de liens (dans VS2008 est LNK2005) car la méthode est définie plus d'une fois. Je comprends cela car AFAIK une spécialisation complète de modèle est la même qu'une simple définition de méthode.

Alors, comment puis-je supprimer cela inline? Le code ne doit pas être dupliqué à chaque utilisation. J'ai cherché sur Google, lu quelques questions ici dans SO et essayé plusieurs des solutions suggérées, mais aucune n'a été construite avec succès (du moins pas dans VS 2008).

Merci!

Chuim
la source
4
Pourquoi voulez-vous supprimer l'inline? Trouvez-vous cela déplaisant esthétiquement? Pensez-vous que cela change la signification de votre code?
Martin York
1
Parce que si cette méthode était "longue" et utilisée dans de nombreux endroits, j'obtiendrais son code binaire copié partout, non? J'ai essayé d'expliquer cela dans la question mais je suppose que ce n'était pas clair ... :)
Chuim
@Martin: Que faire si l'implémentation a besoin de beaucoup d'autres codes qui doivent ensuite être inclus par cet en-tête au lieu du fichier cpp?
sbi

Réponses:

71

Comme pour les fonctions simples, vous pouvez utiliser la déclaration et l'implémentation. Mettez dans votre déclaration d'en-tête:

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

et mettez l'implémentation dans l'un de vos fichiers cpp:

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

N'oubliez pas de supprimer inline (j'ai oublié et j'ai pensé que cette solution ne fonctionnera pas :)). Vérifié sur VC ++ 2005

maxim1000
la source
J'ai déjà essayé quelque chose au moins similaire à celui-ci, mais j'ai eu d'autres erreurs, mais maintenant que vous avez mentionné, j'ai dû oublier de supprimer le inlinecopier / coller while. De cette façon, cela a fonctionné!
Chuim
La même chose s'applique aux fonctions sans modèle (par opposition aux méthodes de classe). J'obtenais la même erreur de l'éditeur de liens pour ma spécialisation de fonction. J'ai déplacé le corps de la spécialisation de fonction dans un fichier .cpp et laissé la déclaration de la spécialisation dans l'en-tête, et tout a fonctionné. Merci!
aldo
Je viens de rencontrer ce problème, et ce qui précède l'a résolu pour moi. En outre, vous devez prendre soin de l'endroit où le compilateur développe le code du modèle. Si cela est fait deux fois, le compilateur se plaint de plusieurs définitions.
Diederik le
4

Vous devez déplacer la définition de spécialisation vers le fichier CPP. La spécialisation de la fonction membre de la classe modèle est autorisée même si la fonction n'est pas déclarée comme modèle.

BostonLogan
la source
3

Il n'y a aucune raison de supprimer le mot-clé en ligne.
Cela ne change en rien la signification du code.

Martin York
la source
Copié du commentaire de la question: Parce que si cette méthode était "longue" et utilisée dans de nombreux endroits, j'obtiendrais son code binaire copié partout, non? J'ai essayé d'expliquer cela dans la question mais je suppose que ce n'était pas clair ... :)
Chuim
1
Non. L'éditeur de liens supprime toutes les copies supplémentaires. Ainsi, dans une application ou une bibliothèque, vous n'auriez qu'une seule instance de la méthode.
Martin York
3
Si le inlinemot - clé aboutit à ce que la fonction soit réellement insérée (le standard dit que le compilateur doit le prendre comme un indice), alors ces copies supplémentaires ne peuvent pas être supprimées. Ce n'est, cependant, qu'un indice pour en ligne (son effet principal est de dire "ne pas générer d'erreurs sur les collisions de liens d'une manière particulière")
Yakk - Adam Nevraumont
2

Si vous souhaitez supprimer l'inline pour une raison quelconque, la solution de maxim1000 est parfaitement valable.

Dans votre commentaire, cependant, il semble que vous pensiez que le mot-clé inline signifie que la fonction avec tout son contenu est toujours intégrée, mais AFAIK qui dépend en fait beaucoup de l'optimisation de votre compilateur.

Citations de la FAQ C ++

Il existe plusieurs façons de désigner une fonction en ligne, dont certaines impliquent le mot clé en ligne, d'autres non. Quelle que soit la façon dont vous désignez une fonction comme étant en ligne, c'est une demande que le compilateur est autorisé à ignorer: le compilateur peut développer en ligne certains, tous ou aucun des endroits où vous appelez une fonction désignée comme étant en ligne. (Ne vous découragez pas si cela semble désespérément vague. La flexibilité de ce qui précède est en fait un énorme avantage: il permet au compilateur de traiter les grandes fonctions différemment des petites, et il permet au compilateur de générer du code facile à déboguer si vous sélectionnez les bonnes options du compilateur.)

Donc, à moins que vous ne sachiez que cette fonction gonflera réellement votre exécutable ou à moins que vous ne vouliez le supprimer de l'en-tête de définition du modèle pour d'autres raisons, vous pouvez réellement le laisser là où il se trouve sans aucun dommage.

Triskeldeian
la source
1

Je voudrais ajouter qu'il y a toujours une bonne raison de conserver le inlinemot - clé si vous avez l'intention de laisser également la spécialisation dans le fichier d'en-tête.

"Intuitivement, lorsque vous spécialisez complètement quelque chose, cela ne dépend plus d'un paramètre de modèle - donc à moins que vous ne fassiez la spécialisation en ligne, vous devez le mettre dans un fichier .cpp au lieu d'un .h ou vous finissez par violer la règle d'une définition unique ... "

Référence: https://stackoverflow.com/a/4445772/1294184

Jordan
la source
0

C'est un peu OT, mais j'ai pensé laisser ça ici au cas où cela aiderait quelqu'un d'autre. Je cherchais sur la spécialisation des modèles sur Google, ce qui m'a conduit ici, et bien que la réponse de @ maxim1000 soit correcte et m'a finalement aidé à résoudre mes problèmes, je ne pensais pas que c'était très clair.

Ma situation est un peu différente (mais assez similaire pour laisser cette réponse je pense) que celle de l'OP. Fondamentalement, j'utilise une bibliothèque tierce avec toutes sortes de classes qui définissent des "types d'état". Le cœur de ces types est simplement enums, mais les classes héritent toutes d'un parent commun (abstrait) et fournissent différentes fonctions d'utilité, telles que la surcharge d'opérateurs et une static toString(enum type)fonction. Chaque statut enumest différent les uns des autres et sans rapport. Par exemple, l'un enuma les champs NORMAL, DEGRADED, INOPERABLE, un autre a AVAILBLE, PENDING, MISSING, etc. Mon logiciel est en charge de gérer différents types de statuts pour différents composants. Il est arrivé que je voulais utiliser les toStringfonctions pour cesenumclasses, mais comme elles sont abstraites, je ne peux pas les instancier directement. J'aurais pu étendre chaque classe que je voulais utiliser, mais j'ai finalement décidé de créer une templateclasse, où le typenameserait le statut concret qui me tenait à enumcœur. Probablement un débat peut avoir lieu à propos de cette décision, mais j'ai senti que c'était beaucoup moins de travail que d'étendre chaque enumclasse abstraite avec une classe personnalisée et d'implémenter les fonctions abstraites. Et bien sûr, dans mon code, je voulais juste pouvoir appeler .toString(enum type)et lui faire imprimer la représentation sous forme de chaîne de cela enum. Puisque tous les enums étaient totalement sans rapport, ils avaient chacun leur propretoStringfonctions qui (après quelques recherches que j'ai apprises) ont dû être appelées en utilisant la spécialisation des modèles. Cela m'a conduit ici. Vous trouverez ci-dessous un MCVE de ce que je devais faire pour que cela fonctionne correctement. Et en fait, ma solution était un peu différente de celle de @ maxim1000.

Il s'agit d'un fichier d'en-tête (grandement simplifié) pour le enums. En réalité, chaque enumclasse était définie dans son propre fichier. Ce fichier représente les fichiers d'en-tête qui me sont fournis dans le cadre de la bibliothèque que j'utilise:

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

en ajoutant cette ligne juste pour séparer le fichier suivant dans un bloc de code différent:

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

}

fichier suivant

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

fichier suivant

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

et cela produit:

BEARS1
TIGERS3

Je ne sais pas si c'est la solution idéale pour résoudre mon problème, mais cela a fonctionné pour moi. Maintenant, quel que soit le nombre de types d'énumérations que je toStringfinis par utiliser, tout ce que j'ai à faire est d'ajouter quelques lignes pour la méthode dans le fichier .cpp, et je peux utiliser la toStringméthode déjà définie des bibliothèques sans l'implémenter moi-même et sans étendre chacune enumclasse que je veux utiliser.

yano
la source