代码之家  ›  专栏  ›  技术社区  ›  Mordachai

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

  •  5
  • Mordachai  · 技术社区  · 15 年前

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

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

    是否没有更自然、更完备的语法,例如:

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

    ScopeGuard 通常在我的代码中。一个明显的用途是确保所有临时CDC对象首先被选择到给定的DC中,然后在作用域出口处被删除,使我的代码即使在特殊情况下也不会泄漏-同时清理编写的代码(愚蠢的多个退出路径和try/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 回复  |  直到 4 年前
        1
  •  2
  •   Community paulsm4    7 年前

    你所问的与前面的一个问题类似,我在这里给出的答案也与此相关。

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

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

    重载函数名前面可以有 & 注: 忽略重载函数名周围的任何冗余括号集(5.1)。]

    在你的情况下 目标 上面列表中的第三个参数是 MakeObjGuard

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

        2
  •  2
  •   Jerry Coffin    15 年前

    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;
    

        3
  •  1
  •   James    15 年前

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

        4
  •  0
  •   AnT stands with Russia    15 年前

    你似乎误解了问题的根源。只要编译器知道赋值/初始化接收端的具体类型,如果typedef定义为

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

    然后是初始化

    SelectObjectBitmap pmf = &CDC::SelectObject; 
    

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

    当然,在没有typedef的情况下也应该可以这样做。


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

    选择对象位图pmf=&CDC::SelectObject;
    

    然后通过 pmf