C ++是一个有自己方法的对象?

时间:2015-02-07 16:06:57

标签: c++ oop

对不起,这可能是一个愚蠢的问题。我显然误解了面向对象编程的基本原理。我习惯了C,现在正在尝试使用C ++。

我在一个名为Button的类中有一些按钮。每个按钮都有不同的功能。我想要写的东西是这样的:

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

但这不起作用("按钮1不是类,命名空间或枚举" - 是的,我知道!)。我知道我可以为每个按钮单独创建一个类:

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

但对我而言,感觉'当我确定它只会有一个成员时,上课是错误的。

我使用的是Allegro 5的GUI库Agui。

修改

感谢您的回复。虽然它们都是有用的(我认为)所有有效答案,但实际上还没有人说过#34;不,你不能用自己独特的方法拥有一个对象,因为......"

因此,例如,如果object1的类型为ObjectClass,则不允许object1拥有object1唯一的方法(成员函数),而是仅拥有 定义为的方法ObjectClass的一部分。是对的吗? 对不起,我没有包括我的实际用例。我更感兴趣的是只是让我的头围绕OOP,这样我就可以自己完成。

EDIT2 更详细地看一下响应,我认为使用lambda表达式是可能的,它不会像我想象的那样。再次感谢

6 个答案:

答案 0 :(得分:7)

自然的C ++方式就像vsoftco解释的那样,具有虚拟和继承。

但是,如果您的Button类已经完成了所有需要,并且按钮之间唯一的变化是要执行的唯一(trhow-away)操作,您可能需要考虑这个替代方法:

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

您的类的这个变体使用 function 对象(将其视为一种函数指针,但使用起来要灵活得多)。

然后您可以将其与 lambda-functions 一起使用,如下例所示:

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

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

答案 1 :(得分:3)

如果按钮具有不同的功能,最好的办法是创建一个BaseButton类,在其中将onclick()标记为virtual(或将其设为纯虚拟,这将使BaseButton一个抽象类),然后从BaseButton派生出彼此的按钮,确保在每个派生类中重写onclick()。然后,您需要通过引用或指针BaseButton来使用按钮,这样您就可以实现所谓的&#34;多态行为&#34;。

例如:

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 */}

};

然后使用它(C ++ 14智能指针)

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

答案 2 :(得分:3)

你可以通过多种方式做到这一点。

  1. 使用Button类,其中按钮对象具有指向调用的方法的指针onClick。在C中你可以使用回调来做到这一点,你也可以在C ++中这样做:

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

    但请注意,函数指针容易出错,编译器无法真正内联,一般应避免使用。此方法也适用于无捕获的lambdas。

    Button red([] { std::cout << "Red button\n"; });
    Button green(&green_button_function);
    
  2. 动态创建具有不同Button方法的不同onClick个对象。 C ++有一种机制来执行此操作,称为模板:

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

    我在这里省略了有关参考的详细信息。 然后,您可以通过以下方式使用带有回调的Button类以及lambdas:

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

    您需要在此方法中使用auto,否则您必须手动指定功能的类型,这是不可能的,例如:对于lambda对象。

  3. 使用vsoftco所示的多态,您可以在其中为每个Button功能创建单独的类。或者,您可以创建一个ButtonAction抽象类,Button具有引用。然后,您在不同的类中实现不同的功能,但保持一个Button类。这称为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(); }
    };
    

    使用此方法需要在Button ButtonAction s

    中构建unique_ptr s
    Button red(std::unique_ptr<ButtonAction>(new RedButtonAction));
    Button green(std::unique_ptr<ButtonAction>(new GreenButtonAction));
    

答案 3 :(得分:2)

你是对的,如果每个按钮基本相同但需要绑定不同的事件处理程序,那么为每个按钮实现一个新类型并不完全正确。

相反,您的Button类型会有一个成员函数,允许用户附加&#34;一个事件处理程序,以及一个调用它的成员函数。

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

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

    friend class UI;

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

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

然后您的用户执行:

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

Button btn;
btn.setOnClickHandler(foo);

您的程序内部会设置一些内容,以便您的窗口管理员(我以上认为它是一个名为UI的类)调用btn.onClick()你,因为你&#34;附加&#34; foo,最终会调用foo

(在现代C ++中,你可能会使用lambda函数来整理它,但上面是一个简单的示例来展示一般的设计理念。)

通过这种方式,您可以将不同的处理程序附加到不同的Button实例,但Button接口本身是稳定的。

这与您在JavaScript中操作DOM的方式类似。

答案 4 :(得分:1)

使用std :: function是关键。如果您的可调用(lambda,函数,成员函数)很大,您将听到虚拟调用和潜在的内存分配。这实现了对单个类型执行不同回调的要求,而没有定义类继承。同样使用统一初始化使得使用lambda构造Button类非常方便,而无需手动创建构造函数。

实例: 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();
}

答案 5 :(得分:1)

您的Agui library支持信号系统,其成员函数为addActionListener

这允许您从agui::ActionListener派生一个类来执行用于一个或多个按钮的特定任务:

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

上面的对象可以通过以下方式附加到按钮的“按下”操作:

SimpleActionListener simpleAL;
button1.addActionListener(&simpleAL);