Как еще добиться «шаблонных указателей на функции»? - PullRequest
2 голосов
/ 18 февраля 2009

Можно ли установить набор шаблонных указателей на функции без необходимости делать это вручную? Вот пример, чтобы проиллюстрировать, какого черта я говорю.

Допустим, у меня есть часто вызываемая функция "write", у меня есть две реализации (write0 и write1), которые я хотел бы иметь возможность динамически переключаться между ними. Эти функции записи основаны на типе аргумента. Один из способов сделать это - использовать шаблонную функцию write (), которая внутренне использует оператор if.

Оказалось, что это достаточно быстро для моих нужд, но теперь мне было интересно, смогу ли я сделать то же самое, используя указатели на функции (просто для удовольствия). Проблема с этим подходом состоит в том, что установка указателей на функции является проблемой. Существуют ли другие способы по существу достичь идеала write (), но без условной (прямой статической диспетчеризации)?

(Другие «правила»: я не могу изменить классы Msg на методы write () и не могу изменить код сайта использования, чтобы заменить Msgs на адаптеры для Msgs.)

FWIW, я нашел эту статью , в основном говоря то же самое, что я говорю здесь.

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

Ответы [ 4 ]

4 голосов
/ 18 февраля 2009

Если вы хотите переключать функции ведения журнала вперед и назад во время работы программы, я думаю, вам нужно вручную установить указатель функции для каждого типа.

Если при запуске достаточно просто выбрать функцию ведения журнала, это можно сделать полностью универсальным способом, даже не зная, для каких типов функция будет вызываться позже:

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

Для каждого типа T первый вызов write<T>() решает, какую функцию записи следует использовать. Более поздние вызовы затем напрямую используют указатель на эту функцию.

1 голос
/ 18 февраля 2009

Вы также можете использовать заголовок FastDelegates Дона Клагстона. Не генерирует никаких накладных расходов во время выполнения и действительно объектно-ориентированных делегатов. Хотя синтаксис их использования не идеален, он немного проще, чем возиться с необработанными указателями на функции.

0 голосов
/ 18 февраля 2009

Существует две оси вариации в письме: выбор write0 / write1 и выбор MsgA / B / C .....

Концептуально это означает, что вам нужны реализации NxM функции write. Конечно, если добавлена ​​реализация записи или тип сообщения, это приводит к соотв. M или N дополнительных функций, которые будут добавлены.

Для обеих осей вы можете выбрать, применять ли их статический или динамический полиморфизм. Статический полиморфизм может быть выполнен с использованием шаблонов или переопределений функций.

Это можно сделать, создав иерархию классов N элементов с M функциями записи в каждом классе. Но это скоро станет кошмаром обслуживания. Если содержимое сообщения также не является полиморфным во время выполнения. Но вопрос о статическом полиморфизме сообщений.

Поскольку полиморфизм во время выполнения исключен из-за слишком сложной (и у вас не может быть виртуальной функции шаблона, которая могла бы уменьшить многословность переопределений), нам нужно реализовать небольшую подпрограмму диспетчеризации типов, преобразующую информацию времени выполнения в компиляцию информация.

Более конкретно: шаблонизируйте основное действие (в примере с именем Tmain) с помощью редактора и вызовите его с правильным аргументом шаблона из 'real' main.

Это исключает использование переменной глобального выбора, но в то же время является объектно-ориентированным и кратким.

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

    }
0 голосов
/ 18 февраля 2009

Почему вы не используете массив указателей на функции?

#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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...