C++11 替代 Java 匿名回调 class

C++11 alternative to the Java anonymous callback class

我意识到我这里的解决方案对于 C++ 来说远非理想,所以我想问一个合适的 C++ 程序员在这种情况下会怎么做。 (C++11)

我有一个 DialogBox class,它存储了一组按钮。目前我有一个纯抽象的内部 class DialogBox::Button 和纯虚函数 virtual void callback() const.

来自 Java 我习惯使用此策略来创建和实例化一个匿名的 class 派生自 Button 就地实现回调函数。像这样:

db.add_button(new Button( "Button text", BUTTONTYPE ) {
    public void callback() {
        // Callback code
}});

这就是促使此 C++ 解决方案的原因。

因此我的 C++ 解决方案看起来像

dialogbox.h

class DialogBox {
public:
    // Abstract class for buttons with callback functions
    class Button;

private:
    /* ...
      stuff 
     */

public:
    /* ...
      stuff 
     */
    const std::vector< unique_ptr<DialogBox::Button> >& get_buttons() const;
    void add_button( unique_ptr<DialogBox::Button>& new_button );
};


class DialogBox::Button {

private:
    /* ...
      stuff
     */

public:
    // Constructor
    Button( const string& button_text, const DialogButtonType button_type = DialogButtonType::NORMAL );

    /* ...
      stuff
     */

    // Virtual callback function
    virtual void callback() const = 0;

};

用法:

// Instantiate
DialogBox db{ /* ... args ... */ };
// Test adding buttons
class Button1 : public DialogBox::Button {
    using DialogBox::Button::Button;
    public: void callback() const {
        // Callback code
    }
};
std::unique_ptr<DialogBox::Button> button1{ new Button1{ "Button1", DialogButtonType::ENTER } };
db.add_button( button1 );

这行得通,但它显然不如 Java 版本干净,而且我确实觉得我在强求一些 C++ 不适合做的事情。

那么,C++ 程序员会怎么做呢?将 Button 作为 class 在概念上似乎是正确的(因为它有内部数据和它自己的行为)。目前我正在考虑使用 lambda 表达式将回调函数传递给 Button 的构造函数,但我想我会就这个问题征求一些专家意见。

C++11 的解决方案是 Button 看起来像这样。为了简洁起见,我跳过了 stringDialogButtonType 参数:

class Button {
public:
    template <typename F>
    Button(F&& f) : cb(std::forward<F>(f))
    { }

    void callback() { cb(); }

private:
    std::function<void()> cb; // type-erased functor for ANY callable
                              // that takes zero arguments
};

这允许您拥有一个 Button 的容器,在它们的 callback 中做完全任意的事情——不需要继承。它还允许您通过在构造过程中为它们提供任意回调函子来即时制作一次性按钮:

db.add_button(std::make_unique<Button>([=]{
     // arbitrary Callback code
}));

作为旁注,add_button 绝对应该按值而不是按引用来获取其 unique_ptr 参数。

考虑一个聚合:

struct Button {
  std::string name;
  std::function<void()> on_click;
};

现在你可以add_button({"hello", []{std::cout<<" world\n";}});

最少的代码,最少的样板文件。

我通常从这种事情开始,只在需要时添加更多基础设施。