0

我正在 SDL 中编写类似按钮的东西,如果我与之交互,它可以做一些事情。我想将每个按钮存储在一个容器中(在列表或双端队列之类的东西中),这样我就可以调用它们自己的绘制函数,也可以检查我点击了哪个。所以,我认为最简单的方法是从一个通用类(“按钮”)实例化它们,这样它们都会有一个位置、颜色等,还有一个函数指针,我可以在实例化它们时将其设置为自定义函数.

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

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

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

4

4 回答 4

1

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

如果您提前知道参数,您可以使用std::function,std::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();
    }
}
于 2013-06-24T14:54:48.493 回答
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 ();

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

于 2013-06-24T15:02:09.590 回答
0

通常的 C 方法是有一个 void* 参数,它可以是 NULL 或者可以指向内容所在的结构。被调用的函数将其转换为正确的结构类型并访问元素。

对于 C++ std::bind 和 std::function 可能会对您有所帮助。

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

于 2013-06-24T14:24:59.020 回答
0

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

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*参数的函数的调用。不过,用户不需要知道这些细节,因为这仅在注册回调期间处理,该回调是使用具体类型完成的。

于 2013-06-24T14:51:41.563 回答