Question

Est-il possible d'établir un ensemble de pointeurs de fonction templated, sans les tracas de le faire manuellement? Voici un exemple pour illustrer ce que le diable dont je parle.

Disons que j'ai une fonction fréquemment appelée « écriture » dont j'ai deux implémentations (write0 et write1) que je voudrais être en mesure de basculer entre dynamiquement. Ces fonctions d'écriture sont basés sur un modèle sur le type d'argument. Une façon de le faire est d'avoir juste une fonction frontal basé sur un modèle d'écriture () qui utilise en interne une instruction if.

Cela s'avère être suffisant pour mes besoins, mais maintenant je me suis demandé si rapide que je peux faire les mêmes en utilisant des pointeurs de fonction (juste pour le plaisir). Le problème avec cette approche est que la mise en place des pointeurs de fonction est un problème. Y at-il d'autres moyens de réaliser essentiellement l'idéal d'écriture () mais sans le conditionnel (expédition statique directe)?

(Autres « règles »:. Je ne peux pas changer les classes Msg d'avoir des méthodes d'écriture (), et je ne peux pas changer le code de site d'utilisation pour remplacer Msgs avec des adaptateurs pour Msgs)

FWIW, j'ai trouvé cet article en fait dire la même chose que je dis ici.

#include <iostream>
using namespace std;

template<typename T> void write0(T msg) { cout << "write0: " << msg.name() << endl; }
template<typename T> void write1(T msg) { cout << "write1: " << msg.name() << endl; }

// This isn't so bad, since it's just a conditional (which the processor will
// likely predict correctly most of the time).
bool use_write0;
template<typename T> void write(T msg) { if (use_write0) write0(msg); else write1(msg); }

struct MsgA { const char *name() { return "MsgA"; } };
struct MsgB { const char *name() { return "MsgB"; } };
struct MsgC { const char *name() { return "MsgC"; } };
struct MsgD { const char *name() { return "MsgD"; } };

// This doesn't work: templates may not be virtual.
#if 0
struct Writer { template<typename T> virtual void write(T msg) = 0; };
struct Writer0 { template<typename T> virtual void write(T msg) { cout << "write0: " << msg.name() << endl; } };
struct Writer1 { template<typename T> virtual void write(T msg) { cout << "write0: " << msg.name() << endl; } };
#endif

int main(int argc, char **argv) {
  use_write0 = argc == 1;

  // I can do this:
  write(MsgA());

  // Can I achieve the following without the verbosity (manual setup, named
  // template instantiations, etc.)?
  void (*pwriteA)(MsgA) = use_write0 ? (void(*)(MsgA)) write0<MsgA> : (void(*)(MsgA)) write1<MsgA>;
  void (*pwriteB)(MsgB) = use_write0 ? (void(*)(MsgB)) write0<MsgB> : (void(*)(MsgB)) write1<MsgB>;
  void (*pwriteC)(MsgC) = use_write0 ? (void(*)(MsgC)) write0<MsgC> : (void(*)(MsgC)) write1<MsgC>;
  void (*pwriteD)(MsgD) = use_write0 ? (void(*)(MsgD)) write0<MsgD> : (void(*)(MsgD)) write1<MsgD>;
  pwriteA(MsgA());
  pwriteB(MsgB());
  pwriteC(MsgC());
  pwriteD(MsgD());

  return 0;
}
Était-ce utile?

La solution

Si vous voulez changer de fonctions d'enregistrement avant et en arrière pendant que le programme fonctionne, je pense que vous devez définir manuellement le pointeur de fonction pour chaque type.

S'il suffit de choisir simplement la fonction d'enregistrement au démarrage, il peut être fait d'une manière totalement générique sans même savoir pour quels types la fonction sera appelée plus tard:

// writer functions
template<typename T> void write0(T msg) { std::cout << 0; };
template<typename T> void write1(T msg) { std::cout << 1; };

// global flag
bool use_write0;

// function pointers for all types
template<typename T>
struct dispatch {
   typedef void (*write_t)(T);
   static write_t ptr;
};

// main write function
template<typename T>
inline void write(T msg) {
   (*dispatch<T>::ptr)(msg);
}

// the fun part
template<typename T>
void autoinit(T msg) {
   if (use_write0)
      dispatch<T>::ptr = &write0<T>;
   else
      dispatch<T>::ptr = &write1<T>;
   // call again for dispatch to correct function
   write(msg);
}

// initialization
template<typename T>
typename dispatch<T>::write_t dispatch<T>::ptr = &autoinit<T>;

// usage example
int main(int argc, char **argv) {
   use_write0 = (argc == 1);
   write("abc");
   return 0;
}

Pour chaque type T le premier appel à write<T>() décide quelle fonction l'écriture doit être utilisée. Plus tard, les appels puis utilisez directement le pointeur de fonction à cette fonction.

Autres conseils

Vous pouvez également utiliser Don Clugston FastDelegates en-tête. Génère pas de frais généraux d'exécution et que ce soit vraiment délégués orienté objet. Alors que la syntaxe pour les utiliser est pas parfait, il est un peu plus simple que de jongler avec des pointeurs de fonction premières.

Pourquoi ne pas utiliser un tableau de pointeurs de fonction?

#include <iostream>
using namespace std;

template<typename T> void write0(T msg) { cout << "write0: " << msg.name() << endl; }
template<typename T> void write1(T msg) { cout << "write1: " << msg.name() << endl; }

template<typename T> struct WriteSelector
{
    static void(* const s_functions[])(T msg);
};
template<typename T> void(* const WriteSelector<T>::s_functions[])(T msg)=
{
    &write0<T>,
    &write1<T>
};

unsigned write_index=0;
template<typename T> void write(T msg)
{
    WriteSelector<T>::s_functions[write_index](msg);
}


struct MsgA { const char *name() { return "MsgA"; } };
struct MsgB { const char *name() { return "MsgB"; } };
struct MsgC { const char *name() { return "MsgC"; } };
struct MsgD { const char *name() { return "MsgD"; } };

void Test()
{
    write(MsgA());
    write(MsgB());
    write(MsgC());
    write(MsgD());
}

int main()
{
    Test();
    write_index=1;
    Test();
    return 0;
}

Il y a deux axises de variation par écrit: le write0 / choix write1 et msgA / B / C .... choix.

Conceptuellement que signifie que vous devez implémentations NxM d'une fonction write. Bien sûr, si une mise en œuvre d'écriture est ajouté, ou un type de message est ajouté, ce qui conduit à resp. M ou N des fonctions supplémentaires à ajouter.

Pour les deux axises vous pouvez choisir de les mettre en œuvre en utilisant le polymorphisme statique ou dynamique. Le polymorphisme statique peut être effectuée à l'aide des modèles ou à l'aide de la fonction overrides.

Il pourrait se faire en créant une hiérarchie de classes d'élément N avec des fonctions d'écriture M dans chaque classe. Mais il allait bientôt devenir un cauchemar de maintenance. À moins que le contenu du message est également la durée d'exécution polymorphes. Mais la question est de polymorphisme statique pour les messages.

Depuis polymorphisme d'exécution est exclue en raison de complexe trop (et vous ne pouvez pas avoir une fonction de modèle virtuel, ce qui diminuerait la verbosité des dérogations), nous devons mettre en œuvre une petite routine de type expédition, la conversion des informations d'exécution dans la compilation informations -temps.

Plus précisément:. L'action principale créer des modèles à (dans l'exemple appelé Tmain) avec l'écrivain à utiliser, et l'appeler avec l'argument de bon modèle de la main « réel »

omet l'utilisation d'une variable de choix « global », mais est orienté objet et concis.

    // twodimensionalpolymorph.cpp
    //

    #include <iostream>

    using namespace std;

    class Write0 {
        public: 
        template< typename tMsg > 
        void operator()( /*const*/ tMsg& msg ) { cout << "write0: " << msg.name() << endl; };
    };

    class Write1 {
        public: 
        template< typename tMsg > 
        void operator()( /*const*/ tMsg& msg ) { cout << "write1: "<< msg.name() << endl; };
    };

    struct MsgA { const char *name() { return "MsgA"; } };
    struct MsgB { const char *name() { return "MsgB"; } };
    struct MsgC { const char *name() { return "MsgC"; } };
    struct MsgD { const char *name() { return "MsgD"; } };

    // the Tmain does the real action
    //
    template< typename Writer >
    int Tmain( Writer& write, int argc, char** args ) {

        write( MsgA() );
        write( MsgB() );
        write( MsgB() );
        write( MsgD() );

        return 0;
    }

    // the main merely chooses the writer to use
    //
    int main( int argc, char** args ) {

        if( argc==1 )
            return Tmain( Write0(), argc, args);
        else
            return Tmain( Write1(), argc, args);

    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top