2010-01-14 89 views
5

这真的是得到正确的地址的实例函数的唯一方法:有没有更好的方法来选择正确的方法重载?

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

它总是让我吃惊得如同地狱愚蠢的,我需要的typedef每个重载函数,我想借此地址。

那么...有没有更好的办法?

编辑:基于人已经提供了答案,我相信我有一个解决方案,我根本需要:即有MakeGuard更自然的语法,推导出正确的选择对象覆盖了我:

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

+0

你可以做到这一点,但它肯定不会很漂亮 - 你基本上最终会在当前使用typedef名称的地方插入typedef的主体。 – James 2010-01-14 16:11:43

+0

希望它不是这个CBitmap:http://thedailywtf.com/Articles/The-cbitmap.aspx – 2010-01-14 18:14:54

回答

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名称,但它确实有助于可读性。

+0

MakeObjGuard与MakePair类似 - 我想让函数推断参数类型并为给定参数生成正确的模板对象(就像MakePair生成一个std :: pair 一样)。因此,我必须为MakeXXX函数提供的任何“帮助”都必须位于其参数列表中,而不是手动选择生成的模板(尽管可以这样做,但它的对立面)。 – Mordachai 2010-01-14 16:24:45

+0

我非常感谢你的问题,但在调用'MakeObjGuard'时,你会遇到与重载函数指针调用'make_pair'一样的麻烦。如果编译器不知道参数类型,则不能推导出模板类型。 – 2010-01-14 16:56:45

+0

至少就目前而言,我很满意我从您和其他人处获得的代码对此主题的帮助。你会得到最完整答案的正式答案。再次感谢 :) – Mordachai 2010-01-14 17:04:54

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(并根据你的编译器使用,你可能已经有这个可用)。

+0

好的,在这种特殊情况下不需要强制转换,无论它是否使用typedef。即使没有强制转换,编译器也需要选择适当的过载。 – AnT 2010-01-14 17:50:54

1

可以尽量避免使用类型定义,但它确实是不漂亮:

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

你似乎误解了问题的根源。只要在分配/初始化的接收方的具体类型是已知的编译器,你不应该做任何努力去“选择”它在所有,如果您的typedef定义为

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

则初始化

SelectObjectBitmap pmf = &CDC::SelectObject; 

需要调用重载解析并选择适当的重载版本的函数,而不需要显式强制转换。这实际上只在C++中的一个地方,当表达式/初始化的右侧的重载决策取决于左侧时。

当然,同样应该没有typedef工作。


OK,看到你实际做的是通过该方法的地址模板的功能依赖参数类型:当然,在这种情况下,你必须要么1)选择合适的过载使用强制转换,或2)通过显式指定模板参数来修复函数参数类型。

当然,你可以通过做

SelectObjectBitmap pmf = &CDC::SelectObject; 

预先选择适当的过载,然后通过pmf到您的模板功能。

相关问题