简体   繁体   English

如何创建接受具有不同签名的函数指针的 C++ 类?

[英]How can I create a C++ class that accepts function pointers with different signatures?

I am relatively new to C++ and I am working on a personal project for practicing where I need to create a class that can take function pointers with different signatures.我对 C++ 比较陌生,我正在做一个个人项目,用于练习我需要创建一个可以使用具有不同签名的函数指针的类。

The idea is that each instance of the class will store a pointer to a specific function and can call that function whenever I want.这个想法是类的每个实例都将存储一个指向特定函数的指针,并且可以随时调用该函数。

To give a better idea of what I want, let me explain with a little bit more detail what I am trying to do.为了更好地了解我想要什么,让我更详细地解释一下我想要做什么。 The project I am working on is a very basic console game and the object I am trying to create is an object that would store details on each location the player can access in the game.我正在处理的项目是一个非常基本的控制台游戏,我尝试创建的对象是一个对象,该对象将存储玩家可以在游戏中访问的每个位置的详细信息。

(DISCLAIMER: I know that most of what I describe later is probably an overkill for a basic console game. I can easily make the whole game in a couple of files using just simple functions and I know how to do that. But the idea here is that I wanted to practice more advanced C++ techniques without having to figure out a complex project. So, since I know how to make a basic console game, I thought it would be a good idea to try and figure out how to achieve the same result but with more advanced techniques) (免责声明:我知道我后面描述的大部分内容对于基本的控制台游戏来说可能是一种矫枉过正。我可以使用简单的函数轻松地将整个游戏制作成几个文件,而且我知道如何做到这一点。但这里的想法是我想练习更高级的 C++ 技术,而不必弄清楚一个复杂的项目。所以,既然我知道如何制作一个基本的控制台游戏,我认为尝试弄清楚如何实现相同的目标是个好主意结果,但使用更先进的技术)

One of the details that I think should be stored is what happens in each location, basically the text that is output to the screen describing what happens and prompting the user to take action.我认为应该存储的细节之一是每个位置发生的事情,基本上是输出到屏幕的文本,描述发生的事情并提示用户采取行动。

Since this would be different for each location, I can't just declare and implement a function in the class.由于每个位置都会有所不同,因此我不能只在类中声明和实现一个函数。

One way of solving this issue is to create a base class with a virtual function and then implement this function in a series of derived classes, each defining a new location.解决这个问题的一种方法是创建一个带有虚函数的基类,然后在一系列派生类中实现这个函数,每个派生类定义一个新的位置。

The problem I have with this approach is that it makes each location a class that can be inherited further and instanced, which I don't need as I will only have 1 instance of each location.我使用这种方法的问题是它使每个位置成为一个可以进一步继承和实例化的类,我不需要它,因为每个位置只有 1 个实例。

I can of course just create 1 instance of the class, but I wanted to see if there is a way to avoid having to create separate classes for each location.我当然可以只创建类的 1 个实例,但我想看看是否有办法避免为每个位置创建单独的类。

This why I started thinking of function pointers.这就是为什么我开始考虑函数指针的原因。

Now, I know I can declare a function pointer and initialise it in a class like that:现在,我知道我可以声明一个函数指针并在这样的类中初始化它:

class Test
{
    public:
        Test(void (*p)())
            : print{p}
        {}
    private:
        void (*print)();
};

That works fine as long as the function returns void and accepts no arguments.只要函数返回 void 并且不接受任何参数,就可以正常工作。

So, I thought maybe I can do that with a template:所以,我想也许我可以用一个模板来做到这一点:

template <typename Function>
class Test
{
    public:
        Test(Function *p)
            : print{p}
        {}
        Function *print;
};

This actually works well.这实际上运作良好。 I can now have a class that accepts different functions with different return types.我现在可以拥有一个接受具有不同返回类型的不同函数的类。

I can create instances of the class in the following way:我可以通过以下方式创建类的实例:

void print();

Test<void ()> a {print};

However, I have one problem with this approach.但是,我对这种方法有一个问题。 Because it is a class template, I can't have a pointer that I want to use to point to instances of Test class regardless of the function that is passed to them.因为它是一个类模板,所以我不能有一个指针来指向 Test 类的实例,而不管传递给它们的函数如何。

For instance, if I declare the following pointer:例如,如果我声明以下指针:

Test<void ()> *b = &a;

There is no way to re-assign that pointer to another instance of Test class unless the function pointer passed to it also returns void and accepts no arguments.除非传递给它的函数指针也返回 void 并且不接受任何参数,否则无法将该指针重新分配给 Test 类的另一个实例。 Otherwise, I have to create a new pointer.否则,我必须创建一个新指针。

Is there a way to avoid that?有没有办法避免这种情况? Is there a better way of achieving what I am looking for than using function pointers?有没有比使用函数指针更好的方法来实现我正在寻找的东西?

Thank you very much and sorry for the long message.非常感谢你,很抱歉给你这么长的消息。

There is no way to re-assign that pointer to another instance of Test class unless the function pointer passed to it also returns void and accepts no arguments.除非传递给它的函数指针也返回 void 并且不接受任何参数,否则无法将该指针重新分配给 Test 类的另一个实例。

And how would you expect that to work?你希望它如何运作? Each Test has a different signature for its print method.每个Testprint方法都有不同的签名。 In the example below, if you assigned action2 to action1 , then how would it know what to pass to the print function?在下面的示例中,如果您将action2分配给action1 ,那么它如何知道要传递给打印函数的内容?

void fun1() {}
void fun2(int) {}

void test() {
  Test<void()> action1= &fun1;
  action1.print();
  Test<void(int)> action2= &fun2;
  action2.print(42);
}

What you want is really simple, thanks to improvements in C++11: std::function<void()> .由于 C++11 的改进,您想要的非常简单: std::function<void()>

Whenever you need to pass some arguments to the function, you'd use a lambda, potentially with captures:每当您需要向函数传递一些参数时,您都可以使用 lambda,可能带有捕获:

#include <functional>

void fun1() {}
void fun2(int) {}

using Action = std::function<void()>;

int main() {
  class MyObject { public: void doSomething() {} } obj;

  Action action1;
  action1 = fun1;
  action1 = []{ fun2(42); };
  action1 = [&obj]{ obj.doSomething(); }; // obj captured by reference
}

So that's pretty much what you should do.所以这就是你应该做的。

Now of course you may ask "hey, but what if I wanted to implement something like std::function myself"?现在当然你可能会问“嘿,但如果我想自己实现像std::function这样的东西怎么办”? It's not a trivial task, if you want to get full functionality of std::function , including small object optimization (ie std::function usually doesn't allocate unless the functor it wraps is "big").如果您想获得std::function全部功能,包括小对象优化(即std::function通常不会分配,除非它包装的函子“大”),这不是一项微不足道的任务。

I wanted to see what can I do if I had a reason to have the each game location using a function that have different return types.我想看看如果我有理由使用具有不同返回类型的函数来获取每个游戏位置,我该怎么办。

You chose to use a common abstraction, ie some class (like Action ) that you can always call the same way, and get to do various things.您选择使用一个通用的抽象,即您可以始终以相同方式调用的某个类(如Action ),并可以做各种事情。 So, what would you like the following code to do?那么,您希望以下代码做什么?

Action someAction;
int result = someAction();
std::string result = someAction();

Now suppose that eg someAction has captured a void() function.现在假设例如someAction已经捕获了一个void()函数。 What should the code that assigns the "result" to an integer or a string do?将“结果”分配给整数或字符串的代码应该做什么? And how would you protect from mistakes, eg if you really wanted someAction() to return an int, but you accidentally used one that returns nothing?您将如何防止错误,例如,如果您真的希望someAction()返回一个 int,但您不小心使用了一个什么都不返回的方法?

Remember that the compiler has to generate code for all of the sites where you call someAction , so it needs to know what to do ahead of time, even if you may otherwise ensure that no "incorrect" calls are made.请记住,编译器必须为您调用someAction所有站点生成代码,因此它需要提前知道要做什么,即使您可以通过其他方式确保不会进行“不正确”的调用。

You could implement a custom Action class on top of std::function that could support std::optional<ResultType> instead of ResultType .您可以在std::function之上实现自定义Action类,该类可以支持std::optional<ResultType>而不是ResultType That way, if the actual result of the functor wrapped in Action was convertible to that ResultType , you'd get a non-null optional, otherwise the optional would be null.这样,如果包装在Action中的函子的实际结果可转换为该ResultType ,您将获得一个非空的可选项,否则该可选项将为空。 And the list of supported result types would need to be pre-determined, ie the compiler wouldn't be able to dynamically add them, but adding a new type should amount to passing an additional type as a template argument, so it'd be easy to support quite many types.并且支持的结果类型列表需要预先确定,即编译器将无法动态添加它们,但添加新类型应该相当于传递一个附加类型作为模板参数,所以它是很容易支持相当多的类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM