我正在写一些像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)
调用函数,该函数无法看到我传递给它的屏幕变量。是否有任何解决方案能够将自定义数量的参数传递给特定函数,并将零参数传递给没有参数的函数?
答案 0 :(得分:1)
您描述定义和调用函数的方式不是很安全。这是因为正如Oli在评论中指出的那样,很容易错误地调用该函数。如果目标函数需要3个参数,并且最终只传递1,则会得到未定义的行为,并且任何事情都可能发生。在这种情况下,"任何"将是一件坏事。
如果您提前知道参数,可以使用std::function
,std::bind
和参数的早期绑定以更安全的方式调用函数。这将允许您在[应] 知道需要传递的参数的位置创建一个函数对象,绑定参数然后设置fooptr
。
以下示例使用您问题中的代码,并使用std::bind
和std::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 ();
注意:对象应该在某处销毁,即你应该使用智能指针存储它们!