Как еще добиться «шаблонных указателей на функции»?

StackOverflow https://stackoverflow.com/questions/560322

Вопрос

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

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

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

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

Кстати, я нашел Эта статья в основном говорю то же самое, что и здесь.

#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;
}
Это было полезно?

Решение

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

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

// 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>() решает, какую функцию записи следует использовать.Более поздние вызовы затем напрямую используют указатель функции на эту функцию.

Другие советы

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

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

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

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

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

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

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

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

Более конкретно:шаблонизировать основное действие (в примере под названием Tmain) с записывающим устройством и вызывайте его с правильным аргументом шаблона из «реального» 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);

    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top