如何编写通用的“getData”函数?

时间:2009-09-16 14:20:27

标签: c++ templates member-function-pointers

我有一个类,比如“CDownloader”,它读取一些XML数据并提供节点名称的访问。它具有一些getter函数,如下所示:

BOOL CDownloader::getInteger ( const CString &name, int *Value );
BOOL CDownloader::getImage   ( const CString &name, BOOL NeedCache, CImage *Image );
BOOL CDownloader::getFont    ( const CString &name, CFont *Font );

我无法更改CDownloader类。相反,我想写一些函数,使用bool标志下载项目,而不是实际名称。像这样:

BOOL DownloadFont( const CDownloader &Loader, bool Flag, CFont *Font )
{
   if (Flag) {
      // first try the "name_1"
      if ( Loader.getFont("name_1", Font) ) return TRUE;
   }
   // if "name_1" fails or disabled by flag, try "name_2"
   return Loader.getFont("name_2", Font);
}

我可以分别编写Download(Font | Integer | Image)函数,但这会导致代码重复。我的想法是写一个模板,但我仍然不知所措:我如何确定我应该从CDownloader类调用什么方法?为每种数据类型专门化模板意味着再次陷入代码重复。将getter函数作为“指向函数的指针”参数传递?但是CDownloader中的getter签名有所不同......

总结一下,问题是:是否可以在CDownloader周围编写一个通用的包装器,还是我必须为每个“get ***”函数复制代码?提前谢谢!

6 个答案:

答案 0 :(得分:1)

只要你有三个不同命名的函数并且需要根据类型选择一个函数,在某些时候你必须有一个重载或一些traits类来选择正确的函数。我不认为有办法解决这个问题。但是,由于调用其中一个函数是唯一需要这个函数的函数,如果这些DownloadXXX()函数的代码多于您向我们展示的代码,那么它可能仍然有意义。

这是使用过载替代方案可能会做的草图。首先,您需要三个相同功能的重载,每个重载调用三个不同功能中的一个。其中一个函数的附加BOOL参数对通用性造成了严重破坏,但我通过让所有函数接受BOOL来解决这个问题,但是其中两个忽略了它:

inline BOOL Load(CDownloader& Loader, const CString &name, int &Value, BOOL)
{return Loader.getInteger(name, &Value);

inline BOOL Load(CDownloader& Loader, const CString &name, CImage &Value, BOOL NeedCache)
{return Loader.getImage(name, NeedCache, &value);

inline BOOL Load(CDownloader& Loader, const CString &name, CFont &Value, BOOL)
{return Loader.getFont(name, &Font);

现在你可以去写那个通用函数了。但是,您需要决定如何处理BOOL

template< typename T >
BOOL Download(const CDownloader &Loader, bool Flag, T &Obj, BOOL NeedCache /*= true*/)
{
   if (Flag) {
      if ( Load(Loader, "name_1", Obj, NeedCache) ) return TRUE;
   }
   return Load(Loader, "name_1", Obj, NeedCache);
}

但是,正如您所看到的,如果Download函数比示例代码复杂得多,那么这真的值得麻烦。否则,增加的复杂性很容易超过增加的通用性带来的收益。

答案 1 :(得分:1)

正如Ates在他的回答中写道,你仍然必须为CDownloader成员编写包装器,因此最终结果可能比直接的方式更冗长,更难理解。例如,这可能是一种可能性(警告:未经测试的代码):

BOOL Get(const CDownloader &Loader, const CString& Name, int* Result)
{
    return Loader.getInteger(Name, Result);
}

BOOL Get(const CDownloader &Loader, const CString& Name, CImage* Result)
{
    return Loader.getImage(Name, SomeDefaultValueForNeedCache, Result);
}

BOOL Get(const CDownloader &Loader, const CString& Name, CFont* Result)
{
    return Loader.getFont(Name, Result);
}


template<class T>
BOOL Download(const CDownloader &Loader, bool Flag, T* Result)
{
   if (Flag) {
      // first try the "name_1"
      if ( Get(Loader, "name_1", Result) ) return TRUE;
   }
   // if "name_1" fails or disabled by flag, try "name_2"
   return Get (Loader, "name_2", Result);
}

试图成为“聪明人”,可以尝试制作一个吸气剂的boost :: fusion :: map,由“getted”类型索引:

fusion::map<
    fusion::pair<int, boost::function<BOOL(const CDownloader&, int*)>,
    fusion::pair<CImage, boost::function<BOOL(const CDownloader&, CImage*)>,
    fusion::pair<CFont, boost::function<BOOL(const CDownloader&, CFont*)>
>
GetterMap = fusion::make_map(
    fusion::make_pair<int>(bind(&CDownloader::getInteger, _1, _2)), 
    fusion::make_pair<CImage>(&CDownloader::getImage, _1, SomeDefaultValueForNeedCache, _2),
    fusion::make_pair<CFont>(&CDownloader::getFont, _1, _2)
);


template<class T>
BOOL Download(const CDownloader &Loader, bool Flag, T* Result)
{
   if (Flag) {
      // first try the "name_1"
      if ( fusion::at<T>(GetterMap)(Loader, "name_1", Result) ) return TRUE;
   }
   // if "name_1" fails or disabled by flag, try "name_2"
   return fusion::at<T>(GetterMap)(Loader, "name_2", Result);
}

如您所见,与直接方式相比的收益并不明显。

答案 2 :(得分:1)

我认为函数对象是最好的,因为你可以适应不同的签名。

struct FontLoader {
    CFont *Font;
    FontLoader() {}
    BOOL operator()(const CDownloader& Loader, bool Flag) {
        if (Flag && Loader.getFont("name_1", Font) ) 
            return TRUE;
        return Loader.getFont("name_2", Font);
    }
};

struct ImageLoader {
    CImage *Image;
    BOOL NeedCache;
    ImageLoader(BOOL nc) : NeedCache(nc) {}
    BOOL operator()(const CDownloader& Loader, bool Flag) {
        if (Flag && Loader.getImage("name_3", NeedCache, Image) ) 
            return TRUE;
        return Loader.getImage("name_4", NeedCache, Image);
    }          
};

template <typename T> // T has application operator CDownloader x bool -> T1
BOOL Download( const CDownloader &Loader, bool Flag, T& func)
{
    return func(Loader, Flag);
}

这些电话看起来像是:

FontLoader Font_func;
BOOL ret1 = Download(Loader, Flag, Font_func);
ImageLoader Image_func(TRUE);
BOOL ret2 = Download(Loader, Flag, Image_func);

传入的结构将包含下载的对象。在C ++ 0x中,您将能够定义一个概念,以便对模板参数T提供更好的类型检查。

答案 3 :(得分:0)

我不认为编写通用包装器最终会减少代码/重复,因为3个getter的方法签名是不同的。无论如何,你都需要围绕这些的包装函数。您可以选择使用3种不同的Download *功能的直接方式。你可能使用宏来将条件逻辑保存在一个中心位置,但这可能会使你的代码严重不可读,而且不值得。

答案 4 :(得分:0)

你可以到某个地方找到一个指向成员函数的指针:

struct X
{
    bool getInt(int* p) const { *p = 42; return true; }
    bool getFloat(float* p) const { *p = 3.14; return true; }
};

template <class Func, class T>
bool load(const X& x, Func f, T* t)
{
    return (x.*f)(t);
}

int main()
{
    int i;
    float f;
    X x;
    load(x, &X::getInt, &i);
    load(x, &X::getFloat, &f);

    //load(x, &X::getFloat, &i);
}

现在getImage方法的例外使它变得更难。可以尝试使用类似boost :: bind / std :: tr1 :: bind实例的工作。

#include <boost/bind.hpp>

struct X
{
    bool getInt(int* p) const { *p = 42; return true; }
    bool getFloat(float* p, bool b) const { *p = 3.14; return b; }
};

template <class Func, class T>
bool load(Func f, T* t)
{
    return f(t);
}

int main()
{
    using namespace boost;
    int i;
    float f;
    X x;
    load(bind(&X::getInt, x, _1), &i);
    load(bind(&X::getFloat, x, _1, true), &f);
}

答案 5 :(得分:-1)

这是一种C-hacky方式。

void* DownloadFont( const CDownloader &Loader, bool Flag, CFont *Font )
{
   if (Flag) {
      // first try the "name_1"
      if ( Loader.getFont("name_1", Font) ) return (void*)1; //access this directly and *die*
   }
   // if "name_1" fails or disabled by flag, try "name_2"
   return (void*)(Loader.getFont("name_2", Font);
}

最后,你将需要一个与整数/字体/图像/ foobars /魔术猴的专业获取相关的逻辑。我只是吮吸它并编写一个Download *()系列。