Как написать универсальную функцию "getData"? - PullRequest
0 голосов
/ 16 сентября 2009

У меня есть класс, скажем, "CDownloader", который читает некоторые данные XML и обеспечивает доступ по именам узлов. Он имеет некоторые функции получения, что-то вроде этого:

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 ...

Подводя итог, возникает вопрос: можно ли написать универсальную оболочку для CDownloader или мне нужно дублировать код для каждой функции "get ***"? Заранее спасибо!

Ответы [ 6 ]

1 голос
/ 16 сентября 2009

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

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.

1 голос
/ 16 сентября 2009

Как пишет Атес в своем ответе, вам все еще нужно написать обертки для членов 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);
}

Как видите, выигрыш по сравнению с простым способом не очевиден.

1 голос
/ 16 сентября 2009

Пока у вас есть три функции с разными именами и вам нужно выбрать одну в зависимости от типа, в какой-то момент вам понадобится либо класс перегрузки, либо класс черт, чтобы выбрать правильную. Я не думаю, что есть способ обойти это. Однако, так как вызов одной из этих функций - единственное, что нуждается в этом, если для этих 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 намного сложнее, чем в вашем примере кода. Иначе добавленная сложность легко перевешивает выгоды, которые приносит увеличенная универсальность.

0 голосов
/ 16 сентября 2009

Вот C-хакерский способ сделать это.

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 * ().

0 голосов
/ 16 сентября 2009

Вы можете получить где-нибудь указатель на функцию-член:

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);
}
0 голосов
/ 16 сентября 2009

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

...