2

我试图让一个类函数采用另一个类未知函数。我希望这有任何意义。我想做的是......如果单击按钮1(或任何声明的按钮),它将执行输入到函数Event__MouseClicked的第一个参数中的任何函数(任何“无效函数”)。

bool MouseClicked( void ){ /**/ };

namespace XE
{
    class Window
    {
    public:
        const char* Title;
    };

    class Button
    {
    public:
        void Event__MouseClicked( void( *Function )( void ) ) {
            if( MouseClicked( ) )
                ( *Function )( );
        }
    };
};

class XorrWindow1 : public XE::Window
{
public:
    XorrWindow1( void ) {
        Initialize( );
    }
protected:
    ~XorrWindow1( void );
private:
    XE::Button Button1;
    XE::Button Button2;
private:
    // EVENT PROTOTYPES
    void DoSomething( void );
    void RandomFunction( void );

    void Initialize( void )
    {
        // INITIALIZE CONTROLS
        // AND OTHER STUFF BELOW
        this->Title = "XorrWindow1";

        // How can I resolve this problem?
        this->Button1.Event__MouseClicked( &XorrWindow1::DoSomething );
        this->Button2.Event__MouseClicked( &XorrWindow1::RandomFunction );
    };
};

void XorrWindow1::DoSomething( void ) {
    ::MessageBoxA( NULL, this->Title, "Button1 clicked!", MB_OK );
};

void XorrWindow1::RandomFunction( void ) {
    ::MessageBoxA( NULL, this->Title, "Button2 clicked!", MB_OK );
};

错误是这样的:

'XE::Button::Event__MouseClicked' : cannot convert parameter 1 from 'void (__thiscall XorrWindow1::* )(void)' to 'void (__cdecl *)(void)'

我完全理解导致错误的原因。但我不知道如何修复它,因为它必须能够采用 Window1 类的任何未知功能。

4

3 回答 3

3

正如 Karel Petranek 所说,您需要模板来适应所有情况。这是通用的方法(STL 到处使用):

template<typename f>
void Event__MouseClicked(f&& func)
{
    if( MouseClicked( ) )
        func();
}

基本上Event__MouseClicked接受一个可调用对象。在您的情况下,您可以像 Marcelo Cantos 所描述的那样称呼它:

Button1.Event__MouseClicked([&]{ DoSomething(); });

但是这种方法的好处是传递任何不带参数的可调用对象都Event__MouseClicked将编译并工作。您可以传递函数指针、结果std::bind、a std::function、lambda 等。

在某些情况下,这比强制接受 a 更优化std::function,例如在接受直接函数指针时。

于 2013-01-20T21:49:55.790 回答
2

您不能将成员函数指针作为自由函数指针传递,因为无法this根据需要捕获和传递它。

这个问题有很多不同的解决方案。如果你有 C++11,最优雅的方法是传递一个std::function<void()>

virtual void Event__MouseClicked(const std::function<void()>& f) {
    if (MouseClicked()) f();
}
⋮
Button1.Event__MouseClicked([&]{ DoSomething(); });

编辑:我几乎忘了提到使用 lambdas,你甚至可以取消回调成员函数:

Button1.Event__MouseClicked([&]{
    ::MessageBoxA( NULL, this->Title, "Button1 clicked!", MB_OK );
});

因为这不是模板函数,所以您可以在派生类中覆盖它。但是请注意,GUI 框架通常不要求您从类Button中派生来自定义它们的行为,并且它们也不会将回调作为事件的一部分传递。常见的模式是按钮保持回调函数并在适当的时候调用它。例如:

class Button {
⋮
public:
    void onClicked(std::function<void(Button*)>& f) { clicked_ = f; }
    void click() { clicked_(this); }
private:
    std::function<void(Button*)> clicked_;

    // Internal click handler responds to OS click events.
    void handleClickEvent(…) { clicked_(this); }
};

以上不是世界上最好的设计,但它应该让您了解我在说什么。

于 2013-01-20T21:44:26.470 回答
1

您将需要使用模板来适应所有可能的类:

        template <typename T>
        void Event__MouseClicked(T *object, void( T::*Function )( void ) ) {
            if( MouseClicked( ) )
                ( object->*Function )( );
        }

利用:

this->Button2.Event__MouseClicked( this, &XorrWindow1::RandomFunction );

请注意,您将失去在子类中覆盖此方法的能力,因为模板函数不能是虚拟的。您还需要将“this”指针与方法指针一起传递。

于 2013-01-20T21:39:47.247 回答