有没有更好的方法来选择正确的方法过载?

Mor*_*hai 5 c++ winapi mfc templates visual-studio-2008

这真的是获取实例函数的正确地址的唯一方法:

typedef CBitmap * (CDC::* SelectObjectBitmap)(CBitmap*);
SelectObjectBitmap pmf = (SelectObjectBitmap)&CDC::SelectObject;
Run Code Online (Sandbox Code Playgroud)

首先,必须创建一个typedef,然后必须使用它来强制编译器在获取其地址时选择正确的重载方法?

是否没有更自然,更自包含的语法,例如:

SelecdtObjectBitmap pmf = &CDC::SelectObject(CBitmap*);
Run Code Online (Sandbox Code Playgroud)

我经常在我的代码中使用ScopeGuard.一个显而易见的用途是确保首先将任何临时CDC对象选入给定DC,然后在范围退出时删除,即使在特殊情况下也使我的代码无泄漏 - 同时清理写入的代码(愚蠢的多个退出路径并尝试/ 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);
Run Code Online (Sandbox Code Playgroud)

它总是让我感到愚蠢,因为我需要输入每个重载函数,我希望得到的地址.

所以...有更好的方法吗?!

编辑:基于人们提供的答案,我相信我有一个解决我的基本需求的方法:即为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));
}
Run Code Online (Sandbox Code Playgroud)

这使我上面的代码更改为:

ScopeGuard guard_canvas = MakeSelectObjectGuard(memdc, &canvas);
Run Code Online (Sandbox Code Playgroud)

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

对于那些可能在这里寻找同一件事的非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;
};
Run Code Online (Sandbox Code Playgroud)

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

谢谢所有回复和评论的人!:d

Rob*_*edy 2

您所问的问题与之前的问题类似,我在那里给出的答案也与此相关。

\n\n\n\n

从第 13.4/1 节(“重载函数的地址”[over.over]):

\n\n
\n

在某些上下文中,不带参数的重载函数名称的使用被解析为函数、指向函数的指针或指向重载集中特定函数的成员函数的指针。函数模板名称被认为是在此类上下文中命名一组重载函数。所选函数的类型与上下文中所需的目标类型相匹配。目标可以是

\n\n
    \n
  • 正在初始化的对象或引用(8.5、8.5.3),
  • \n
  • 作业的左侧(5.17),
  • \n
  • 函数的参数 (5.2.2),
  • \n
  • 用户定义运算符的参数 (13.5),
  • \n
  • 函数、运算符函数或转换的返回值 (6.6.3),或
  • \n
  • 显式类型转换(5.2.3、5.2.9、5.4)。
  • \n
\n\n

重载函数名称前面可以添加&运算符。在未列出的上下文中,不得在没有参数的情况下使用重载函数名称。[注意:重载函数名称周围的任何冗余括号都将被忽略 (5.1)。]

\n
\n\n

就您而言,上面列表中的目标是第三个,即函数的参数MakeObjGuard。但是,我怀疑这是一个函数模板,并且模板的类型参数之一是函数指针的类型。编译器有一个 Catch-22。在不知道选择哪个重载的情况下,它无法推断模板参数类型,并且在不知道参数类型的情况下,它无法自动选择您想要的重载。

\n\n

因此,你需要帮助它。您可以像现在一样对方法指针进行类型转换,也可以在调用函数时显式指定模板参数类型:MakeObjGuard<SelectObjectBitmap>(...)。无论哪种方式,您都需要知道类型。您并不严格需要为函数类型提供 typedef 名称,但它确实有助于提高可读性。

\n