De que outra forma de alcançar “ponteiros de função templated”?
-
05-09-2019 - |
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;
}
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);
}