Example: Suppose we are developing a simple graphical interface:

We have windows and controls. What we want:

  • There is a base window class that implements GUI functionality that is hidden from the user.
  • From the base window, the user inherits a specific window.
  • There is a button class, the instance of which the user can add to his window.
  • The user can transfer the method of his window to the button, and it will be executed when clicking on this button.

I am interested in how to reach the last point.

Similarity of pseudo-code:

class BaseWindow; class Button { public: Button(BaseWindow* parent) { parent->AddButton(this); } void SetBehavior(/*Owners pointer and owner's methos*/) { /* Save Owner pointer and owner's method*/ } void Clicked(/*coords*/) { if(/*coords == my coords*/) { /*Call Owner's method*/ } } }; class BaseWindow { vector<Button*> Buttons; WindowClicked(/*coords*/) { for (std::vector<Button*>::iterator it = Buttons.begin(); it != Buttons.end(); ++it) { it->Clicked(/*coords*/); } } public: void AddButton(Button* butt) { Buttons<<butt; } }; class UserWindow:public BaseWindow { Button MyButton; public: void FunctionForButton(Button* butt){ cout<<"Say Hello, my sweet button";} UserWindow():MyButton(this) { MyButton.SetBehavior(/*Put here my Function for Button and my pointer*/); } }; 

    1 answer 1

    In your particular case, you can solve the problem with the most basic means of the core language, i.e. do so:

    1. As the type of the method being called, we use the type BehaviorPtr - a pointer to the method of the BaseWindow class BaseWindow

       typedef void (BaseWindow::*BehaviorPtr)(Button *); 

      This is exactly the type that the Button::SetBehavior function parameter will have. The Button class will hold both the BaseWindow *parent pointer and the BehaviorPtr behavior pointer.

      Those. Button knows only about BaseWindow (it BaseWindow knows about it), but knows nothing about the heirs of BaseWindow .

    2. Calling the target method from a Button is done simply as

       (parent->*behavior)(this); 
    3. During UserWindow initialization, UserWindow pass to Button pointer to this type using static_cast

       MyButton.SetBehavior(static_cast<BehaviorPtr>(&UserWindow::FunctionForButton)); 

      since bringing a pointer-to-member class up the hierarchy in C ++ requires an explicit static_cast .

    Everything. Now, when executing (parent->*behavior)(this) user class method UserWindow::FunctionForButton will be correctly called inside the button (see http://coliru.stacked-crooked.com/a/d3ca67866d3dc2c8 )

    However, this approach will not be particularly flexible. You can achieve better results by using standard functional objects instead of method pointers. And for complete isolation from the "recipient" type, you need the functionality of closure - std::bind (available in C ++ 98 in a limited form), and type erasure functionality - std::function (not in C ++ 98). However, you can try to take analogues from Boost. Or even implement type erasure in the style of std::function manually.

    • Thank you for such a detailed response. Your solution is really simple and lightweight. - Ep1c
    • And is it possible, using the same language tools, to achieve the transfer of a method without using static_cast? (We are good developers, we will not force the user to write such complex structures) - Ep1c
    • @ Ep1c: Within this approach, static_cast not static_cast . However, the user can be relieved of the obligation to do it on his side, by making the SetBehavior method button templates and transferring the static_cast there: template <typename T> void SetBehavior(T behavior) { this->behavior = static_cast<BehaviorPtr>(behavior); } template <typename T> void SetBehavior(T behavior) { this->behavior = static_cast<BehaviorPtr>(behavior); } . ( ideone.com/zM6rLA ) - AnT
    • Yes, a more beautiful solution for the user, but will it not lead to an error if the user passes the method of another class or just the address of the global function? But in general, I understood the idea. - Ep1c
    • @ Ep1c: static_cast is not a reinterpret_cast . static_cast will not allow you to make a completely wrong cast (a completely foreign class or a normal function will not work). Such static_cast only allows to bring pointers to members up and down the hierarchy. Of course, there is a danger here - you can pass there a pointer to the SomeOtherUserWindow member (also inherited from BaseWindow ), which is not in the UserWindow . But this is your responsibility. - AnT