web-dev-qa-db-fra.com

Méthode de modèle virtuel C++

J'ai un cours abstrait (je sais qu'il ne sera pas compilé de cette façon, mais c'est pour comprendre ce que je veux faire):

class AbstractComputation {
    public:
        template <class T> virtual void setData(std::string id, T data);
        template <class T> virtual T getData(std::string id);
};

class Computation : public AbstractComputation {
    public:
        template <class T> void setData(std::string id, T data);
        template <class T> T getData(std::string id, T data);
};

Ainsi, lorsque j'appelle setData<double>("foodouble", data), je souhaite que le double identifié par foodouble (mécanisme interne qui n'est pas la principale préoccupation ici) soit défini sur la double donnée.

Alors, comment faire ça? 

Je pense qu’il peut y avoir un moyen de taper quelque chose comme virtual void setData<double>(std::string id, double data) mais je ne sais pas comment le faire.

28
Vincent

Le problème est que vous ne pouvez pas mélanger facilement le polymorphisme temporel statique (modèles) avec le polymorphisme d'exécution. La raison pour laquelle le langage n'autorise pas la construction particulière dans votre exemple est qu'il existe différents types potentiellement infinis qui pourraient instancier votre fonction de membre de modèle, ce qui signifie que le compilateur devrait générer du code pour envoyer de manière dynamique ces nombreux types, ce qui est infaisable.

Il y a différentes choses qui peuvent être faites ici pour contourner la limitation, essentiellement en supprimant le polymorphisme statique ou dynamique. La suppression du polymorphisme dynamique de l'équation peut être réalisée en fournissant un type non dérivé de, pour stocker les mappages <key,value>, puis en proposant le modèle qui le résout uniquement au niveau de base:

class AbstractComputation {
public:
   template <typename T>
   void setData( std::string const & id, T value ) {
      m_store.setData( id, value );
   }
   template <typename T>
   T getData( std::string const & id ) const {
      return m_store.getData<T>( id );
   }
protected:
   ValueStore m_store;
};

Les classes dérivées peuvent désormais accéder à la variable ValueStore à partir de la base et le polymorphisme n'est pas nécessaire. (Cela peut aussi être fait en implémentant la fonctionnalité directement dans AbstractComputation mais il est probablement logique de séparer les problèmes)

L'autre option consiste à conserver le polymorphisme à l'exécution, mais à supprimer le polymorphisme statique. Cela peut être fait en effaçant le type sur la classe de base, puis en l'envoyant à la fonction appropriée (sans modèle) prenant les arguments type-effacé. La version la plus simple de ceci utilise juste boost::any:

class AbstractComputation {
public:
   template <typename T>
   void setData( std::string const & id, T value ) {
      setDataImpl( id, boost::any( value ) );
   }
   template <typename T>
   T getData( std::string const & id ) const {
      boost::any res = getDataImpl( id );
      return boost::any_cast<T>( res );
   }
protected:
   virtual void setDataImpl( std::string const & id, boost::any const & value ) = 0;
   virtual boost::any getDataImpl( std::string const & id ) const = 0;
};

La manière dont le type d'effacement est implémenté sous le capot est intéressante, mais il est important de noter qu'un boost::any est un type concret (sans modèle) pouvant stocker tout type en interne en utilisant le type effacement sur les arguments, et permet en même temps de récupérer les données en toute sécurité.

Dans certains cas, il peut être suffisant de déplacer le modèle du niveau de la méthode au niveau de la classe, par exemple:

#include <iostream>

template<typename T>
class AbstractComputation {
public:
    virtual void setData(std::string id, T data)
    {
        std::cout << "base" << std::endl;
    }
};

template<typename T>
class Computation : public AbstractComputation<T> {
public:
    virtual void setData(std::string id, T data)
    {
        std::cout << "derived" << std::endl;
    }
};

int main()
{
    AbstractComputation<int> *x = new Computation<int>();

    x->setData("1", -1);

    delete x;
    return 0;
}
14
TobiMcNamobi

Premièrement, vous ne pouvez pas avoir les fonctions de modèle virtual. Comme les modèles sont résolus au moment de la compilation, virtual ne fonctionnera pas car le compilateur ne saurait pas quel modèle choisir. Voir ici , pour plus d'informations à ce sujet.

2
Tony The Lion

Utilisez boost::any pour accepter la donnée, puis, lorsque vous définissez, récupérez le type approprié.

1
Nim

Vous pouvez probablement utiliser boost::any dans votre cas.

virtual void setData(std::string id, boost::any data);

C'est un emballage qui peut encapsuler presque n'importe quoi.

Plus d'infos sur un sujet similaire dans cette réponse .

1
jpalecek

Si vous connaissez à l'avance la liste des types possibles, le préprocesseur peut vous aider:

#define MY_CLASSES MYTYPE(int) MYTYPE(float) MYTYPE(double)

class AbstractComputation {
    public:
#     define MYTYPE(T) virtual void setData(std::string id, T data)=0;\
                       virtual void getData(std::string id, T& dst_data)=0;
      MY_CLASSES
#     undef MYTYPE
};

class Computation : public AbstractComputation {
    public:
#     define MYTYPE(T) virtual void setData(std::string id, T data){std::cout<<"writing: "<<data<<std::endl;}\
                       virtual void getData(std::string id, T& dst_data){dst_data=0;/*put your actual implementation here*/}
      MY_CLASSES
#     undef MYTYPE
};

Si vous ne connaissez pas la liste complète des types possibles, votre problème est peut-être insoluble. Le type effacé, comme mentionné par d'autres, peut également aider .. mais pas dans toutes les circonstances.

1
user396672