2

I realise that the solution I have here is far from ideal with C++, so I'm asking what a proper C++ programmer would do in this situation. (C++11)

I have a DialogBox class, which stores a collection of buttons. At the moment I have a pure abstract inner class DialogBox::Button with the pure virtual function virtual void callback() const.

From Java I'm used to using this strategy to create and instantiate an anonymous class deriving from Button in-place which implements the callback function. Something like this:

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

which is what prompted this C++ solution.

My C++ solution therefore looks like

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;

};

Usage:

// 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 );

This works, but it's clearly not as clean as the Java version and certainly feels like I'm shoehorning in something that C++ is not designed to do.

So, how would a C++ programmer do this? It seems conceptually right to have Button as a class (since it has internal data and its own behaviour). At the moment I'm thinking of using a lambda expression to pass in the callback function to Button's constructor, but I thought I'd get some expert opinion on the subject.

8
  • This doesnt seems like a good practice. Commented May 30, 2015 at 12:01
  • I realise this, which is why I've come in search of good-practice solutions. Commented May 30, 2015 at 12:01
  • 2
    What version of C++? C++11 has lambda functions. Commented May 30, 2015 at 12:03
  • 2
    lambda and/or std::function may help. Commented May 30, 2015 at 12:03
  • @pathfinderelite yes, it's C++11. Do you suggest I use lambdas as described in my last paragraph? Commented May 30, 2015 at 12:12

2 Answers 2

4

The C++11 solution would be to have Button look something like this. I'm skipping the string and DialogButtonType parameters for brevity:

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
};

This allows you to have a container of Buttons that do completely arbitrary things in their callbacks - no inheritance necessary. It also allows you to great one-off buttons on the fly by providing them with an arbitrary callback functor as part of the construction process:

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

As a side-note, add_button should definitely take its unique_ptr argument by value, not by reference.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks, this looks good. Should I also be returning the vector of unique_ptrs by value rather than by reference?
@JMAA By reference. You'd have to move it out otherwise - unless that's the semantic you want, in which case, sure, by value.
Why have a class Button at all? It seems useless. typedef std::function<void()> Button -- it isn't really a button, just an action to take when clicked.
@Yakk I assume the name had some significance for something?
0

Consider an aggregate:

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

now you can add_button({"hello", []{std::cout<<" world\n";}});

Minimal code, minimal boilerplate.

I generally start with this kind of thing, and only add more infrastructure as needed.

2 Comments

This looks nicely minimal. Sorry for the noob question, but does it allow me to deal with const buttons and other access controls?
@jmaa The button is taken by-value, and its constness means you cannot change the action/name, but not much else.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.