具有函数指针的类,可以在不同的实例中调用多种函数

时间:2013-06-24 14:15:23

标签: c++ class function pointers

我正在写一些像SDL中的按钮,如果我与它交互,它可以做些什么。我想将每个按钮存储在一个容器中(类似于列表或双端队列),所以我可以调用自己的绘图函数,我也可以检查我点击的那个。所以,我认为最简单的方法是从泛型类(“Button”)实例化它们,所以它们都有一个位置,颜色等等,还有一个函数指针,当我实例化它时我可以将它设置为自定义函数

我的问题是,当我在类中定义函数指针时,我必须声明一个参数列表,但可能会出现需要不同参数列表的情况(例如:

MyButton button() 
button.fooptr=quit; //quit is a void function with no arguments
button.fooptr();
//another case
button.fooptr=print; //print is a void function which takes an SDL_Surface* as an argument.
button.fooptr(screen);

我试图将函数指针定义为void (*fooptr)(...),如果我的函数有(...)作为参数,它会很有效,但如果我尝试用fooptr(screen)调用函数,该函数无法看到我传递给它的屏幕变量。是否有任何解决方案能够将自定义数量的参数传递给特定函数,并将零参数传递给没有参数的函数?

4 个答案:

答案 0 :(得分:1)

您描述定义和调用函数的方式不是很安全。这是因为正如Oli在评论中指出的那样,很容易错误地调用该函数。如果目标函数需要3个参数,并且最终只传递1,则会得到未定义的行为,并且任何事情都可能发生。在这种情况下,"任何"将是一件坏事。

如果您提前知道参数,可以使用std::functionstd::bind和参数的早期绑定以更安全的方式调用函数。这将允许您在[应] 知道需要传递的参数的位置创建一个函数对象,绑定参数然后设置fooptr

以下示例使用您问题中的代码,并使用std::bindstd::function对其进行扩展,以安全统一的方式调用函数。它还包括使用std::reference_wrapper允许通过引用传递参数的示例。

#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <functional>

struct SDLSurface 
{
    std::string str;
};

struct Button
{
    std::function<void(void)> fooptr;
};

void quit()
{
    std::cout << "called quit()" << std::endl;
}

void print(SDLSurface *surface)
{
    std::cout << "called print(\"" << surface->str << "\")" << std::endl;
}

void print_string(const std::string& str)
{
    std::cout << "called print_string(\"" << str << "\")" << std::endl;
}


int main()
{
    std::vector<std::unique_ptr<Button>> buttons;
    std::unique_ptr<Button> b;

    // Create a button and assign a function that takes no parameters
    b.reset(new Button());
    b->fooptr = quit;
    buttons.push_back(std::move(b));

    // Create a button and assign a function
    b.reset(new Button());
    SDLSurface  surface;
    b->fooptr = std::bind(print, &surface);
    buttons.push_back(std::move(b));

    // Create a button and assign a function taking a parameter by reference
    b.reset(new Button());
    std::string string_param;
    // Since we are passing by reference and want to bind to an existing variable
    // we need to use a reference wrapper.
    b->fooptr = std::bind(
        print_string,
        std::reference_wrapper<const std::string>(string_param));
    buttons.push_back(std::move(b));

    // Call all functions setting the value of string_param before we do.
    surface.str = "hello";
    string_param = "world";
    for(auto it = buttons.begin(); it != buttons.end(); ++it)
    {
        (*it)->fooptr();
    }
}

答案 1 :(得分:0)

通常的C方式是使void *参数可以为NULL或者可以指向内容所在的结构。被调用的函数将其转换为适当的结构类型并访问元素。

对于C ++ std :: bind和std :: function可能对你有帮助。

如果你真的想要使用简单的函数,你可以查看正确的va_arg用法,(我认为)你需要至少一个参数才能开始访问其余的函数,并且函数可能很难处理它们。请注意......仅限于POD参数类型。

答案 2 :(得分:0)

按下按钮是一个应包含基本信息的事件(请查看现有框架/语言中的回调/事件)基本按钮的界面应仅支持该特定用例。如果您需要某种特定类型的按钮来调用不同类型的函数,则需要以不同方式处理。一种选择是使用std::bind来调整触发的事件的接口(以最简单的形式没有args)和要调用的函数(注入表面)。

struct Button {
   std::function<void()> callback;

   void pressed() { callback(); }
   void register(std::function<void()> const &cb) {
      callback = cb; 
   }
};
struct ButtonWithSurface : Button {
...
    SDLSurface *surface;
    void register(std::function<void(SDLSurface*)> const &cb) {
       Button::register(std::bind(cb,surface);
    }

从基础Button的所有用户可以执行的操作是按下按钮,尽管在ButtonWithSurface情况下会触发对某个功能的调用采用SDLSurface*参数。用户仍然不需要知道这些细节,因为只有在使用具体类型完成的回调注册期间才能处理。

答案 3 :(得分:0)

如果您不熟悉闭包(并且不想了解它们),请定义函数对象:

class BtnFunctional {
public:
    virtual void Call () = 0;
    virtual ~BtnFunctional () {
    }
};

class QuitBtnFunctional : public BtnFunctional {
public:
    virtual void Call () {
        // call quit function
    }
};

class PrintBtnFunctional : public BtnFunctional {
private:
    ScreenType * theScreen;
public:
    PrintBtnFunctional (ScreenType * screen) : theScreen (screen) {
    }

    virtual void Call () {
        // call print function, e.g. print (theScreen);
    }
};

// ...
btn.fooptr = new QuitBtnFunctional ();
btn.fooptr.Call ();
btn.fooptr = new PrintBtnFunctional (screen);
btn.fooptr.Call ();

注意:对象应该在某处销毁,即你应该使用智能指针存储它们!