corfizz
corfizz

Reputation: 33

C++ an object with its own method?

I'm sorry, this is probably a stupid question. I am obviously misunderstanding something fundamental about object oriented programming. I am used to C and am now trying to use C++.

I have some buttons in a class called Button. Each button does something different. What I want to write is something like this:

Button button1;
Button button2;
...
void button1::onClick () {
    ...
}
void button2::onClick () {
    ...
}

But that does not work ("button 1 is not a class, namespace or enumeration" - yes I know!). I know I could just make a separate class for each button:

class button1_class : public Button {
public:
     void onclick () {
        ...
     }
} button1;
class button2_class : public Button {
     ...
}

But to me it 'feels' wrong to make a class when I know for sure it will only have one member.

I'm using Agui, a GUI library for Allegro 5.

EDIT

Thanks for the responses. While they are all helpful and (I think) all valid answers, nobody has actually said yet "no you cannot have an object with its own unique method because..."

So for example, if object1 is of type ObjectClass then object1 is not allowed to have a method (a member function) that is unique to object1, but rather possesses only the methods that are defined as part of ObjectClass. Is that right? I'm sorry I did not include my actual use case. I was kind of more interested in just getting my head around OOP so that I can do it properly on my own.

EDIT2 Looking at the responses in more detail I suppose it is possible with lambda expressions, it's just not in the way I imagined it. Thanks again

Upvotes: 2

Views: 663

Answers (6)

jepio
jepio

Reputation: 2281

You can do this in many ways.

  1. Using a Button class where button objects have a pointer to methods that are invoked onClick. In C you would do this using a callback and you can also do it that way in C++:

    class Button {
        using funType = void(void);
    public:
        Button(funType* callback) : function(callback) { }
        void onClick() { function(); }
    private:
        funType* function;
    };
    

    However do take note that function pointers are error prone, can't really be inlined by the compiler, and should generally be avoided. This method also works with capture-less lambdas.

    Button red([] { std::cout << "Red button\n"; });
    Button green(&green_button_function);
    
  2. Creating different Button objects with different onClick methods on the fly. C++ has a mechanism to do this called templates:

    template <class Fun>
    class Button {
    public:
        Button(Fun f) : functor(f) { }
        void onClick() { functor(); }
    private:
        Fun functor;
    };
    
    template <class Fun>
    Button<Fun> make_button(Fun f) { return Button<Fun>(f); }
    

    I am omitting details such as references on purpose here. You could then use the Button class with callbacks as well as lambdas in the following way:

    auto green = make_button([] { std::cout << "Green button pressed!\n"; });
    auto red = make_button(&red_button_function);
    

    You need to use auto with this method because otherwise you would have to specify the type of the functionality by hand, which is not possible e.g. for lambda objects.

  3. Using polymorphism as shown by vsoftco, where you create separate classes for each Button functionality. Or you can make a ButtonAction abstract class to which Button has a reference. Then you implement different functionalities in different classes, but stay with one Button class. This is known as the strategy pattern:

    class ButtonAction {
    public:
        virtual void onClick() = 0;
    };
    
    class Button {
    public:
        Button(std::unique_ptr<ButtonAction> action) : 
            action_(std::move(action)) {}
        void onClick() { action_->onClick();  }
    private:
        std::unique_ptr<ButtonAction> action_;
    };
    
    class RedButtonAction : public ButtonAction {
        void onClick() override { red(); }
    };
    class GreenButtonAction : public ButtonAction {
        void onClick() override { green(); }
    };
    

    Using this method requires constructing Buttons from ButtonAction unique_ptrs

    Button red(std::unique_ptr<ButtonAction>(new RedButtonAction));
    Button green(std::unique_ptr<ButtonAction>(new GreenButtonAction));
    

Upvotes: 3

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385104

You're right in that, if each button is fundamentally the same but needs different event handlers bound to it, implementing a new type for each one is not quite right.

Instead your Button type would have a member function that allows users to "attach" an event handler, and a member function to invoke it.

class Button
{
public:
    Button()
       : onClickHandler()
    {}

    void setOnClickHandler(std::function<void()> callback)
    {
       onClickHandler = callback;
    }

    friend class UI;

private:
    void onClick()
    {
       onClickHandler();
    }

    std::function<void()> onClickHandler;
};

Then your user does:

void foo()
{
   std::cout << "Some buttons do this!\n";
}

Button btn;
btn.setOnClickHandler(foo);

And your program's internals will set up things such that your window manager (above I've assumed that it's some class called UI) invokes btn.onClick() for you, which, since you "attached" foo, will end up invoking foo.

(In modern C++ you'd probably make use of lambda functions to tidy this up, but the above is a simple example to showcase the general design idea.)

In this way, you can attach different handlers to different Button instances, but the Button interface itself is stable.

This is similar to how, for example, you manipulate the DOM in JavaScript.

Upvotes: 2

Drew Dormann
Drew Dormann

Reputation: 63745

Your Agui library supports a signaling system, with the member function addActionListener.

This allows you to derive a class from agui::ActionListener to perform the specific task intended for one or more buttons:

class SimpleActionListener : public agui::ActionListener
{
public:
    virtual void actionPerformed(const agui::ActionEvent &evt)
    {
        std::cout << "Button pushed" << std::endl;
    }
};

The object above can be attached to a button's "press" action with:

SimpleActionListener simpleAL;
button1.addActionListener(&simpleAL);

Upvotes: 1

rparolin
rparolin

Reputation: 508

Using a std::function is the key here. You will have the virtual call overheard and potential memory allocation if your callable (lambda, function, member function) is large. This achieves your requirements of a single type executing different callbacks without defining an class inheritance. Also using uniform initialization makes it very convenient to construct Button class with a lambda without manually creating a constructor.

Live example: http://coliru.stacked-crooked.com/a/f9007c3f103f3ffe

#include <functional>
#include <vector>
using namespace std;

struct Button
{
  function<void()> OnClick;
};

int main()
{
  vector<Button> buttons = 
  {
      {[] { printf("Button0::OnClick()\n"); }},
      {[] { printf("Button1::OnClick()\n"); }},
      {[] { printf("Button2::OnClick()\n"); }},
  };

  for(auto&& button : buttons)
    button.OnClick();
}

Upvotes: 1

Christophe
Christophe

Reputation: 73366

The natural C++ way is to do as vsoftco explained, with virtuals and inheritance.

However, if your Button class has already everything needed, and the only thing that changes between the buttons is the unique (trhow-away) action to be performed, you may want to consider this alternative:

class Button {
    function<void()>  f; 
public: 
    Button(function<void()> mf) : f(mf) {}
    void onClick() { f();  }
};

This variant of your class uses a function object (think of it as a kind of function pointer but much more flexible to use).

You can then use it with lambda-functions as in this example:

int main(int ac, char**av) 
{
    Button button1([&]() { cout << "Hello 1!\n"; });
    Button button2 ([]() { cout << "Hello 2!\n"; });

    button1.onClick();
    button2.onClick();
}

Upvotes: 7

vsoftco
vsoftco

Reputation: 56547

If the buttons have different functionalities, best thing to do is to create a BaseButton class in which you mark the onclick() as virtual (or make it pure virtual, which will make BaseButton an abstract class), then derive each other button from BaseButton, making sure to override onclick() in each derived class. You then need to use the buttons via a reference or pointer to a BaseButton, this way you achieve what is called "polymorphic behaviour".

For example:

class BaseButton
{
    virtual void onclick() {/*implement here or declare pure virtual*/}
};

class RedButton: public BaseButton /* overrides only onclick */
{
    void onclick() override { /*specific implementation for Red Buttons */}
};

class ShinyRedButton: public RedButton /* overrides only onclick */
{
    void onclick() override { /*specific implementation for Shiny Red Buttons */}

};

then use it like (C++14 smart pointers)

std::unique_ptr<BaseButton> bb = new ShinyRedButton;
bb->onclick(); // will pick up the "right" ShinyRedButton::onclick()` function

Upvotes: 3

Related Questions