web-dev-qa-db-fra.com

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

Considérant toujours que l'en-tête suivant, contenant ma classe basée sur un modèle, est inclus dans au moins deux fichiers .CPP, ce code est compilé 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, LNK2005) en raison de la définition de la méthode plus d'une fois. Je comprends cela car, selon les informations dont je dispose, une spécialisation de modèle complète est identique à une définition de méthode simple.

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

Merci!

75
Chuim

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

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

et mettez la mise en œuvre 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 je pensais que cette solution ne fonctionnerait pas :)) . Vérifié sur VC++ 2005

61
maxim1000

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

4
BostonLogan

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

1
Martin York

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 en ligne mais AFAIK est en réalité très dépendante de l'optimisation de votre compilateur.

Citant depuis le C++ FAQ

Il existe plusieurs façons de désigner une fonction en ligne, dont certaines qui impliquent le mot clé en ligne, d'autres pas. Peu importe comment tu Désigner une fonction comme inline, il s’agit d’une requête du compilateur autorisé à ignorer: le compilateur peut développer en ligne certains, tous ou aucun des endroits où vous appelez une fonction désignée par inline. (Ne vous déconcentrez pas si cela semble désespérément vague. La flexibilité de Ci-dessus est en fait un énorme avantage: elle permet au compilateur de traiter les grandes fonctions Différemment des petites fonctions, plus elle le laisse également générer un code facile à déboguer si vous sélectionnez le bon compilateur options.)

Ainsi, à moins que vous ne sachiez que cette fonction va réellement gonfler votre exécutable ou si vous ne souhaitez pas la supprimer de l'en-tête de définition de modèle pour d'autres raisons, vous pouvez la laisser telle quelle sans dommage.

1
Triskeldeian

C'est un peu OT, mais je pensais laisser ça ici au cas où ça aiderait quelqu'un d'autre. J'étais en train de googler à propos de la spécialisation des modèles qui m'avait conduit ici, et bien que la réponse de @ maxim1000 soit correcte et m'aide finalement à comprendre 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) de celle du PO. Fondamentalement, j'utilise une bibliothèque tierce avec tous les types de classes qui définissent les "types d'état". Le cœur de ces types est tout simplement enums, mais les classes héritent toutes d'un parent commun (abstrait) et fournissent différentes fonctions utilitaires, telles que la surcharge d'opérateur et une fonction static toString(enum type). Chaque statut enum est différent l’un de l’autre et indépendant. Par exemple, une enum a les champs NORMAL, DEGRADED, INOPERABLE, une autre a AVAILBLE, PENDING, MISSING, etc. Mon logiciel est chargé de gérer différents types de statuts pour différents composants. C'est pour cette raison que je voulais utiliser les fonctions toString pour ces classes enum, mais comme elles sont abstraites, je ne pouvais pas les instancier directement. J'aurais pu prolonger chaque classe que je voulais utiliser, mais j'ai finalement décidé de créer une classe template, dans laquelle typename serait le statut concret enum dont je me souciais. On peut probablement débattre de cette décision, mais j’ai eu l’impression que c’était beaucoup moins de travail que d’étendre chaque classe abstraite enum avec une coutume et d’implémenter les fonctions abstraites. Et bien sûr, dans mon code, je voulais simplement pouvoir appeler .toString(enum type) et lui faire imprimer la représentation sous forme de chaîne de cette enum. Étant donné que toutes les enums étaient entièrement indépendantes les unes des autres, elles avaient chacune leur propre fonction toString qui (après quelques recherches que j'ai apprises) a dû être appelée à l'aide de 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 réalité, ma solution était un peu différente de celle de @ maxim1000.

C'est un fichier d'en-tête (grandement simplifié) pour enums. En réalité, chaque classe enum a été définie dans son propre fichier. Ce fichier représente les fichiers d’en-tête qui m’ont été fournis avec 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
  }
};

ajouter 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 génère:

BEARS1
TIGERS3

Aucune idée si c'est la solution idéale pour résoudre mon problème, mais cela a fonctionné pour moi. Maintenant, peu importe le nombre de types d’énumération que j’utilise, j’ai besoin d’ajouter quelques lignes pour la méthode toString dans le fichier .cpp, et je peux utiliser la méthode toString des bibliothèques déjà définies sans la mettre en œuvre moi-même et sans étendre chaque classe enum que je veux utiliser.

0
yano