Есть ли лучший способ выбрать правильный метод перегрузки? - PullRequest
5 голосов
/ 14 января 2010

Действительно ли это единственный способ получить правильный адрес для функции экземпляра:

typedef CBitmap * (CDC::* SelectObjectBitmap)(CBitmap*);
SelectObjectBitmap pmf = (SelectObjectBitmap)&CDC::SelectObject;

Сначала нужно создать typedef, а затем использовать его, чтобы заставить компилятор выбрать правильный перегруженный метод при получении его адреса?

Нет ли более естественного и автономного синтаксиса, такого как:

SelecdtObjectBitmap pmf = &CDC::SelectObject(CBitmap*);

Я часто использую ScopeGuard в своем коде. И одно из очевидных применений заключается в том, чтобы гарантировать, что любые временные объекты CDC сначала выбираются в заданный контроллер домена, а затем удаляются при выходе из области, делая мой код свободным от утечек даже при исключительных обстоятельствах - и одновременно очищая написанный код (глупые множественные пути выхода и попытаться / catch и т. д., чтобы попытаться удалить все выбранные объекты из данного CDC).

Итак, более полный пример того, что я сейчас вынужден делать, выглядит так:

// get our client rect
CRect rcClient;
GetClientRect(rcClient);

// get the real DC we're drawing on
PAINTSTRUCT ps;
CDC * pDrawContext = BeginPaint(&ps);

// create a drawing buffer
CBitmap canvas;
canvas.CreateCompatibleBitmap(pDrawContext, rcClient.Width(), rcClient.Height());

CDC memdc;
memdc.CreateCompatibleDC(pDrawContext);

//*** HERE'S THE LINE THAT REALLY USES THE TYPEDEF WHICH i WISH TO ELIMINATE ***//
ScopeGuard guard_canvas = MakeObjGuard(memdc, (SelectObjectBitmap)&CDC::SelectObject, memdc.SelectObject(&canvas));

// copy the image to screen
pDrawContext->BitBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(), &memdc, rcClient.left, rcClient.top, SRCCOPY);

// display updated
EndPaint(&ps);

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

Так ... есть ли лучший способ?!

РЕДАКТИРОВАТЬ: Основываясь на ответах, предоставленных людьми, я считаю, что у меня есть решение для моей основной потребности: т.е. иметь более естественный синтаксис для MakeGuard, который выводит правильный переопределение SelectObject для меня:

template <class GDIObj>
ObjScopeGuardImpl1<CDC, GDIObj*(CDC::*)(GDIObj*), GDIObj*> MakeSelectObjectGuard(CDC & dc, GDIObj * pGDIObj)
{
    return ObjScopeGuardImpl1<CDC, GDIObj*(CDC::*)(GDIObj*), GDIObj*>::MakeObjGuard(dc, (GDIObj*(CDC::*)(GDIObj*))&CDC::SelectObject, dc.SelectObject(pGDIObj));
}

Из-за чего мой код изменится на:

ScopeGuard guard_canvas = MakeSelectObjectGuard(memdc, &canvas);

/////////////////////////////////////////////// ///////////

Для тех, кто может искать здесь не-MFC версию того же самого:

//////////////////////////////////////////////////////////////////////////
//
// AutoSelectGDIObject
//  selects a given HGDIOBJ into a given HDC,
//  and automatically reverses the operation at scope exit
//
// AKA:
//  "Tired of tripping over the same stupid code year after year"
//
// Example 1:
//  CFont f;
//  f.CreateIndirect(&lf);
//  AutoSelectGDIObject select_font(*pDC, f);
//
// Example 2:
//  HFONT hf = ::CreateFontIndirect(&lf);
//  AutoSelectGDIObject select_font(hdc, hf);
//
// NOTE:
//  Do NOT use this with an HREGION.  Those don't need to be swapped with what's in the DC.
//////////////////////////////////////////////////////////////////////////

class AutoSelectGDIObject
{
public:
    AutoSelectGDIObject(HDC hdc, HGDIOBJ gdiobj) 
        : m_hdc(hdc)
        , m_gdiobj(gdiobj)
        , m_oldobj(::SelectObject(m_hdc, gdiobj))
    {
        ASSERT(m_oldobj != m_gdiobj);
    }

    ~AutoSelectGDIObject()
    {
        VERIFY(m_gdiobj == ::SelectObject(m_hdc, m_oldobj));
    }

private:
    const HDC       m_hdc;
    const HGDIOBJ   m_gdiobj;
    const HGDIOBJ   m_oldobj;
};

/////////////////////////////////////////////// ///////////

Спасибо Всем, кто ответил и прокомментировал! : D

Ответы [ 4 ]

2 голосов
/ 14 января 2010

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

Из раздела 13.4 / 1 («Адрес перегруженной функции» [over.over]):

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

  • инициализируемый объект или ссылка (8.5, 8.5.3),
  • левая сторона задания (5.17),
  • параметр функции (5.2.2),
  • параметр пользовательского оператора (13.5),
  • возвращаемое значение функции, операторской функции или преобразования (6.6.3) или
  • явное преобразование типов (5.2.3, 5.2.9, 5.4).

Имя функции перегрузки может предшествовать оператор &. Перегруженное имя функции не должно использоваться без аргументов в контекстах, отличных от перечисленных. [ Примечание: любой избыточный набор скобок вокруг имени перегруженной функции игнорируется (5.1). ]

В вашем случае, target из приведенного выше списка является третьим параметром вашей MakeObjGuard функции. Тем не менее, я подозреваю, что это шаблон функции, и одним из параметров типа для шаблона является тип указателя на функцию. Компилятор имеет Catch-22. Он не может определить тип параметра шаблона, не зная, какая перегрузка выбрана, и он не может автоматически выбрать, какую перегрузку вы имеете в виду, не зная тип параметра.

Поэтому вам нужно помочь. Вы можете либо привести тип указателя метода, как вы делаете сейчас, либо вы можете явно указать тип аргумента шаблона при вызове функции: MakeObjGuard<SelectObjectBitmap>(...). В любом случае, вам нужно знать тип. Вам не обязательно иметь имя typedef для типа функции, но это, безусловно, помогает удобочитаемости.

2 голосов
/ 14 января 2010

Вам не нужно использовать typedef. Вы просто используете typedef, чтобы создать имя для типа, затем используете этот тип, чтобы определить указатель, и в typecast для инициализации указателя. Если вы действительно хотите, вы можете использовать тип непосредственно для обоих из них, но в итоге вы повторяете один и тот же (часто длинный) тип как для определения, так и для преобразования типа. Для несколько упрощенного, автономного примера:

struct XXX {
    int member() { return 0; }
    int member(int) { return 1; }
};

int main() {     
    int (XXX::*pmfv)(void) = (int (XXX::*)())&XXX::member;
    int (XXX::*pmfi)(int) = (int (XXX::*)(int))&XXX::member;
    return 0;
}

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

Я считаю, что C ++ 0x, auto должны позволить сократить первый пример, приведенный выше, до чего-то вроде этого:

auto pmf = (int (XXX::*)())&XXX::member;

Это должно упростить избегание typedef (и в зависимости от того, какой компилятор вы используете, он может быть уже доступен).

1 голос
/ 14 января 2010

Вы можете избежать использования typedef, но на самом деле это не очень красиво:

void foo(int){
    std::cout << "int" << std::endl;
}

void foo(float){
    std::cout << "float" << std::endl;
}

int main()
{
    typedef void (*ffp)(float);

    ffp fp = (void (*)(float)) foo; // cast to a function pointer without a typedef

    ((ffp)fp)(0);
}

Лучше придерживаться typedef.

0 голосов
/ 14 января 2010

Вы, похоже, неправильно поняли корень проблемы. Пока конкретный тип на принимающей стороне назначения / инициализации известен компилятору, вы не должны прилагать никаких усилий для его «выбора» вообще, если ваш typedef определен как

typedef CBitmap * (CDC::* SelectObjectBitmap)(CBitmap*); 

затем инициализация

SelectObjectBitmap pmf = &CDC::SelectObject; 

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

Конечно, то же самое должно работать и без typedef.


ОК, видя, что на самом деле вы передаете адрес метода шаблонной функции в качестве зависимого типа аргумента: конечно, в этом случае вы должны либо 1) выбрать правильную перегрузку, используя приведение или 2) исправить тип параметра функции, явно указав аргумент шаблона.

Конечно, вы можете предварительно выбрать правильную перегрузку, выполнив

SelectObjectBitmap pmf = &CDC::SelectObject; 

и затем передайте pmf функции вашего шаблона.

...