如何实现“模板化函数指针”?

时间:2009-02-18 09:05:48

标签: c++ templates function-pointers

是否可以建立一组模板化的函数指针,而无需手动执行此操作?这是一个例子来说明我在说什么。

假设我有一个频繁调用的函数“write”,其中我有两个实现(write0和write1),我希望能够动态切换。这些写函数是在参数类型上模板化的。一种方法是只使用模板化的前端函数write(),它在内部使用if语句。

这足以满足我的需求,但现在我想知道我是否可以使用函数指针(只是为了好玩)做同样的事情。这种方法的问题是设置函数指针是一件麻烦事。有没有其他方法可以实现write()的理想但没有条件(直接静态调度)?

(其他“规则”:我无法将Msg类更改为write()方法,并且我无法更改使用站点代码以使用Msgs的适配器替换Msgs。)

FWIW,我发现this article基本上说的是我在这里说的一样。

#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 个答案:

答案 0 :(得分:4)

如果你想在程序运行时来回切换日志记录功能,我认为你必须手动设置每种类型的函数指针。

如果仅仅在启动时选择日志记录功能就足够了,它可以完全通用的方式完成,甚至不知道稍后将调用该函数的类型:

// 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 :(得分:1)

你也可以使用Don Clugston的FastDelegates标题。不会产生任何运行时开销,也不会产生真正面向对象的委托。虽然使用它们的语法并不完美,但它比摆弄原始函数指针要简单一些。

答案 2 :(得分:0)

为什么不使用函数指针数组?

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

答案 3 :(得分:0)

写作中存在两个变化轴:write0 / write1选项和MsgA / B / C ....选项。

从概念上讲,这意味着您需要write函数的NxM实现。当然,如果添加了写入实现,或者添加了消息类型,则会导致resp。要添加的M或N个额外功能。

对于两个轴,您可以选择是使用静态还是动态多态实现它们。静态多态性可以使用模板或使用函数覆盖来完成。

可以通过在每个类中创建具有M个写函数的N元素类层次结构来完成。但它很快就会成为维护的噩梦。除非消息内容也是运行时多态的。但问题是关于消息的静态多态性。

由于运行时多态性因为过于复杂而被排除(并且你不能使用虚拟模板函数,这会降低覆盖的冗长程度),我们需要实现一个小型调度例程,将运行时信息转换为编译时间信息。

更具体地说:使用writer-to-use来模拟主要操作(在名为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);

    }