Pergunta

É possível estabelecer um conjunto de ponteiros de função templated, sem o incômodo de fazê-lo manualmente? Aqui está um exemplo para ilustrar o que diabos eu estou falando.

Vamos dizer que tenho uma função frequentemente chamado de "escrita" de que eu tenho duas implementações (write0 e write1) que eu gostaria de ser capaz de alternar entre dinamicamente. Estas funções de gravação são modeladas sobre o tipo de argumento. Uma maneira de fazer isso é ter apenas uma gravação função templated front-end () que utiliza internamente uma declaração se.

Esta acaba por ser rápido o suficiente para minhas necessidades, mas agora eu estava me perguntando se eu posso fazer o mesmo usando ponteiros de função (apenas por diversão). O problema com esta abordagem é que a criação de ponteiros de função é um aborrecimento. Existem outras maneiras de atingir essencialmente o ideal de write (), mas sem o condicional (expedição estática directa)?

(Outros "regras":. Eu não posso mudar as classes Msg ter write () métodos, e eu não posso mudar o código do site uso para substituir Msgs com adaptadores para Msgs)

FWIW, eu achei este artigo basicamente dizendo a mesma coisa que estou dizendo aqui.

#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;
}
Foi útil?

Solução

Se você quiser funções de log alternar enquanto o programa é executado, eu acho que você tem que configurar manualmente o ponteiro de função para cada tipo.

Se é apenas o suficiente para escolher a função de registro na inicialização, ele pode ser feito de uma forma totalmente genérico, mesmo sem saber para quais tipos de função será chamado mais tarde:

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

Para cada tipo T a primeira chamada para write<T>() decide qual função de gravação deve ser utilizado. Mais tarde chamadas, em seguida, usar diretamente o ponteiro de função para essa função.

Outras dicas

Você também pode usar de Don Clugston FastDelegates cabeçalho. Não gera sobrecarga de runtime qualquer e verdadeiramente delegados orientadas a objetos. Embora a sintaxe para usá-los não é perfeito, é um pouco mais simples do que mexer com ponteiros de função brutos.

Por que você não usar uma matriz de ponteiros de função?

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

Existem dois axises de variação na escrita: a escolha write0 / write1 eo .... escolha MsgA / B / C.

Conceitualmente isso significa que você precisa implementações NXM de uma função write. Claro que, se uma implementação de escrita é adicionado, ou um tipo de mensagem é adicionado, isso leva a resp. M N ou funções adicionais a ser adicionado.

Para ambos os axises você pode escolher se para implementá-las usando estática ou polimorfismo dinâmico. O polimorfismo estático pode ser feito usando modelos ou usar substituições de função.

Pode ser feito através da criação de uma hierarquia de classes elemento N com funções de gravação M em cada classe. Mas logo se tornaria um pesadelo de manutenção. A menos que o conteúdo da mensagem também é tempo de execução polimórfico. Mas a pergunta é sobre polimorfismo estático para as mensagens.

Uma vez que o polimorfismo em tempo de execução está descartada por causa de muito elaborado (e você não pode ter uma função de modelo virtual, o que diminuiria a verbosidade de substituições), precisamos implementar uma pequena rotina de envio de tipo, conversão de informações de tempo de execução em compilação informações -time.

Mais especificamente:. Templatize a ação principal (no exemplo chamado Tmain) com o uso escritor-to-e chamá-lo com o argumento de modelo desde o main 'real'

Este omite o uso de uma variável 'global' escolha, ainda é orientada a objetos e concisa.

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

    }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top