成员函数指针和继承

Posted

技术标签:

【中文标题】成员函数指针和继承【英文标题】:member function pointers and inheritance 【发布时间】:2012-04-05 00:16:42 【问题描述】:

因此,为了自己的方便,我正在开发一个简单的 win32 包装器,但我遇到了一个稍微复杂的问题。

这有很多其他成员,但我省略了一点,只留下有问题的成员。

class Windows::AbstractWindow

public:
     void InstallHandler(UINT msgName, void (Windows::AbstractWindow::*)(HWND, UINT, WPARAM, LPARAM));

private:
     std::map<UINT, void (Windows::AbstractWindow::*)(HWND, UINT, WPARAM, LPARAM)> HandlerIndex;

;

(为了记录,在这种情况下,Windows 是我制作的各种类和对象的命名空间)

有点讨厌,但请解释一下我的过程和推理。我有一个名为 AbstractWindow 的类,它以非常面向对象的方式包含窗口的大部分功能。

我现在正在研究一种获取私有成员函数的方法,并通过指向它们的指针将它们存储在映射中,这些指针由它们应该处理的 Windows 消息标识。这样,当 Windows 过程接收到一条消息时,它会通过这个映射来查看您是否为它安装了一个处理程序。如果有,它会调用该函数并退出。它没有调用 DefWindowProc 并退出。很简单。

然而,这个对象永远不应该被实例化,而只是应该被继承和扩展。问题是,该映射的函数指针声明属于 AbstractWindow 类型,它不允许我存储从 AbstractWindow 继承的类型的成员函数指针。例如,

class BasicWindow : public Windows::AbstractWindow

public:
    BasicWindow() 
    
         InstallHandler(WM_CREATE, &create);
    

private:
    void Create(HWND, UINT, WPARAM, LPARAM) 

...产生错误:

error C2664: 'Windows::AbstractWindow::InstallHandler' : cannot convert parameter 2 from 'void (__thiscall BasicWindow::* )(HWND,MSG,WPARAM,LPARAM)' to 'void (__thiscall Windows::AbstractWindow::* )(HWND,UINT,WPARAM,LPARAM)'

因为指针类型不一样,尽管是从基类继承的。那么有没有人想提出一个解决方案,同时仍然保持这种方法?如果没有,我也愿意接受您认为可以使消息处理比这种方式更方便的建议。

【问题讨论】:

【参考方案1】:

您应该阅读 Curiously Recurring Template Pattern (http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)。

基本上,您将基类转换为模板,并带有指定子类的模板参数。

如果你看到它可能会更容易理解:

namespace Windows

     template <typename T>
     class AbstractWindow
     
     public:
          void InstallHandler(UINT msgName, void (T::*)(HWND, UINT, WPARAM, LPARAM));

     private:
          std::map<UINT, void (T::*)(HWND, UINT, WPARAM, LPARAM)> HandlerIndex;

     ;


class BasicWindow : public Windows::AbstractWindow< BasicWindow >

public:
    BasicWindow() 
    
         InstallHandler(WM_CREATE, &BasicWindow::Create);
    

private:
    void Create(HWND, UINT, WPARAM, LPARAM) 
;

【讨论】:

哇...这真是太聪明了。并且真的没有太多期望包装器的最终用户记得添加。只需在顶部添加那个小模板参数就可以了。是的,我会用这个,非常感谢:P【参考方案2】:

您面临的问题是您正试图以相反的方式进行转换。函数指针在其this 参数中是逆变的(即,指向基类的函数的函数指针将用于指向派生类的方法的函数指针,反之亦然)。你可以:

只需强制转换(使用static_cast,因为它与隐式转换相反)。只要你能确保你永远不会在不合适的类上调用该方法(例如NotABasicWindow().*method()),它就可以正常工作。

摆脱该方案,并注册通用函数(即任何可以调用的东西,而不是成员函数指针)。你会使用例如。 std::function&lt;void(HWND, UINT, WPARAM, LPARAM)&gt; 作为您的处理程序类型并注册将知道其窗口的处理程序(例如 lambda 函数)。

lambda functions are a feature of C++11。它们大致对应于带有活页夹的函数对象;他们创建了一个匿名函数,可以从它们所在的范围内引用变量。一个例子是

[=](HWND wnd, UINT i, WPARAM wp, LPARAM lp)  this->printHandler(wnd, i, wp, lp); 

它会记住this,因此它会在调用时为当前对象调用printHandler(创建lambda的代码的当前,而不是调用代码)。当然,调用方法的对象也可以是另外一个参数。

Lambda 以及其他函数对象(即定义了operator() 的对象)可以转换为std::function 对象并将其存储为std::function 对象。

【讨论】:

哇,你在上一个建议中所说的几乎所有内容都直奔我的脑海。什么是纯函数,什么是 lambda,如何在不作为成员函数的情况下按实例注册所述函数? @FatalCatharsis:我将“纯函数”改写为“通用函数”以避免与函数式编程中使用的术语混淆。另外,我添加了一些关于 lambdas 的简介。【参考方案3】:

这种性质的东西会起作用吗...?附言。我使用 typedef 作为函数签名。

BasicWindow() 
    
         InstallHandler(WM_CREATE, reinterpret_cast<void (__thiscall Windows::AbstractWindow::* )(HWND,UINT,WPARAM,LPARAM)>(&create));
    

【讨论】:

不,不起作用。运行时错误:运行时检查失败 #0 - ESP 的值未在函数调用中正确保存。这通常是调用使用一种调用约定声明的函数和使用另一种调用约定声明的函数指针的结果。

以上是关于成员函数指针和继承的主要内容,如果未能解决你的问题,请参考以下文章

具有类私有成员和函数指针的继承结构

不同类的成员函数指针的C++映射

指向父类成员函数的函数指针

C++|详解类成员指针:数据成员指针和成员函数指针及应用场合

向下转换指向成员函数的指针

C++函数指针与成员函数指针