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

时间:2010-01-14 15:33:45

标签: c++ visual-studio-2008 templates mfc winapi

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

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

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

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

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

我经常在代码中使用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);

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

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

编辑:基于人们提供的答案,我相信我有一个解决我的潜在需求的方法:即为MakeGuard提供更自然的语法,为我推导出正确的SelectObject覆盖: < / p>

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

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

感谢所有回复&amp;的人评论! :d

4 个答案:

答案 0 :(得分:2)

您实际上不需要使用typedef。您只是使用typedef为类型创建名称,然后使用该类型定义指针,并在类型转换中初始化指针。如果你真的想要,你可以直接使用这两种类型,但最终你会为定义和类型转换重复相同(通常很长)的类型。有关简化的独立示例:

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

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

从第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)。 ]

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

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

答案 2 :(得分:1)

你可以避免使用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。

答案 3 :(得分:0)

您似乎误解了问题的根源。只要编译器知道赋值/初始化的接收端的具体类型,你根本不应该努力“选择”它,如果你的typedef被定义为

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

然后初始化

SelectObjectBitmap pmf = &CDC::SelectObject; 

需要调用重载决策并选择正确的函数重载版本而无需显式强制转换。当表达式/初始化的右侧的重载分辨率取决于左侧时,这实际上只是C ++中的一个位置。

当然,没有typedef也一样。


好的,看到你实际在做的是将方法的地址作为依赖参数类型传递给模板函数:当然,在这种情况下,您必须1)通过使用强制转换来选择正确的重载,或者2)通过显式指定模板参数来修复函数参数类型。

当然,你可以通过

预先选择适当的过载
SelectObjectBitmap pmf = &CDC::SelectObject; 

然后将pmf传递给您的模板函数。