【问题标题】:How can I create a C++ class that accepts function pointers with different signatures?如何创建一个接受具有不同签名的函数指针的 C++ 类?
【发布时间】:2020-11-19 14:20:36
【问题描述】:

我对 C++ 比较陌生,我正在从事一个个人项目,以练习我需要创建一个可以采用具有不同签名的函数指针的类。

这个想法是类的每个实例都将存储一个指向特定函数的指针,并且可以随时调用该函数。

为了更好地了解我想要什么,让我更详细地解释一下我想要做什么。我正在做的项目是一个非常基本的控制台游戏,我正在尝试创建的对象是一个对象,它将存储玩家在游戏中可以访问的每个位置的详细信息。

(免责声明:我知道我稍后描述的大部分内容对于基本的主机游戏来说可能是多余的。我可以使用简单的函数轻松地将整个游戏制作成几个文件,而且我知道该怎么做那个。但是这里的想法是我想练习更高级的 C++ 技术,而不必弄清楚一个复杂的项目。所以,既然我知道如何制作一个基本的控制台游戏,我认为尝试和计算是个好主意了解如何使用更先进的技术实现相同的结果)

我认为应该存储的细节之一是每个位置发生的事情,基本上是输出到屏幕的文本,描述发生的事情并提示用户采取行动。

由于每个位置都不同,我不能只在类中声明和实现一个函数。

解决此问题的一种方法是创建一个具有虚函数的基类,然后在一系列派生类中实现该函数,每个派生类都定义一个新位置。

这种方法的问题是它使每个位置成为一个可以进一步继承和实例化的类,我不需要,因为每个位置只有一个实例。

我当然可以只创建该类的 1 个实例,但我想看看是否有办法避免为每个位置创建单独的类。

这就是我开始考虑函数指针的原因。

现在,我知道我可以声明一个函数指针并在这样的类中初始化它:

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

只要函数返回 void 并且不接受任何参数,它就可以正常工作。

所以,我想也许我可以用模板做到这一点:

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

这实际上效果很好。我现在可以拥有一个接受具有不同返回类型的不同函数的类。

我可以通过以下方式创建类的实例:

void print();

Test<void ()> a {print};

但是,我对这种方法有一个问题。因为它是一个类模板,所以无论传递给它们的函数如何,我都不能有一个指针来指向 Test 类的实例。

例如,如果我声明以下指针:

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

除非传递给它的函数指针也返回 void 并且不接受任何参数,否则无法将该指针重新分配给 Test 类的另一个实例。否则,我必须创建一个新指针。

有没有办法避免这种情况?有没有比使用函数指针更好的方法来实现我正在寻找的东西?

非常感谢您,很抱歉给您这么长的信息。

【问题讨论】:

  • 为什么不同位置的类型会不同?
  • @user253751:因为虚函数需要不同。逻辑是有道理的;虚函数的典型实现将函数指针存储在表中。这是一个很小的开销,但这里不需要。然而,我认为开销仍然值得,因为它允许您使用教科书中的标准 C++。
  • 假设您无论如何都想通过一个指针调用您的函数,我相信您必须使所有函数都具有相同的返回类型。它们还必须具有相同的调用签名,但您可以尽早将其他参数绑定到您的函数。一个工具是std::function,它可以绑定普通函数以及带有成员函数的对象和lambdas。后者提供了另一个工具来制作适配器(用于绑定额外的参数)。
  • 当然,lambda 也可以适应返回类型不匹配的函数。但是,如果您自己编写这些函数,如果实际用例(通过唯一函数指针调用)对此没有任何用途,我不明白为什么您必须具有不同的返回类型。
  • 很可能,函数不会有不同的返回类型。但由于我在练习,我试图想象一个他们可能会遇到的场景,并试图找到解决它的方法。

标签: c++ templates c++14


【解决方案1】:

除非传递给它的函数指针也返回 void 并且不接受任何参数,否则无法将该指针重新分配给 Test 类的另一个实例。

您希望它如何工作?每个Test 对其print 方法都有不同的签名。在下面的示例中,如果您将action2 分配给action1,那么它如何知道将什么传递给打印函数?

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

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

由于 C++11 的改进,您想要的非常简单:std::function&lt;void()&gt;

当您需要向函数传递一些参数时,您会使用 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
}

所以这就是你应该做的。

现在您当然可能会问“嘿,但是如果我想实现类似std::function 自己的东西怎么办?”如果您想获得std::function 的全部功能,包括小对象优化(即std::function 通常不会分配,除非它包装的函子是“大”),这不是一项简单的任务。

我想看看如果我有理由让每个游戏位置使用具有不同返回类型的函数,我该怎么办。

您选择使用一个通用的抽象,即某个类(如Action),您可以始终以相同的方式调用,并开始做各种事情。那么,你想让下面的代码做什么呢?

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

现在假设例如someAction 捕获了 void() 函数。将“结果”分配给整数或字符串的代码应该做什么?以及如何防止错误,例如如果您真的希望 someAction() 返回一个 int,但您不小心使用了一个不返回任何内容的整数?

请记住,编译器必须为您调用someAction的所有站点生成代码,因此它需要提前知道要做什么,即使您可以确保没有进行了“不正确”的调用。

您可以在std::function 之上实现自定义Action 类,该类可以支持std::optional&lt;ResultType&gt; 而不是ResultType。这样,如果包裹在Action 中的函子的实际结果可转换为ResultType,您将得到一个非空的可选项,否则该可选项将为空。并且支持的结果类型列表需要预先确定,即编译器将无法动态添加它们,但添加新类型应该相当于传递一个额外的类型作为模板参数,所以它是易于支持多种类型。

【讨论】:

  • 非常感谢您的回复,Unslander。关于你引用的部分,我没想到它会起作用。我只是说它不起作用,我需要一些东西来实现我正在寻找的东西。我会给 std::function 一个尝试,看看我是否可以做我想做的事。非常感谢。
  • 我想做的是你在帖子末尾讨论的部分。看看是否有可能让Action 接受具有不同返回类型的函数。但它可能很难防止错误是有道理的,所以即使我能做到,它也可能不是最好的主意。我将不得不进一步研究std::function 以尝试您建议的可选ResultType 解决方案。
  • @AbdelrahmanSaid 我看到的最大问题是您试图覆盖 C++ 的静态类型以运行一些动态类型层。这通常意味着提示这种情况的设计是单调的,需要重新考虑。如果意图是返回变体,则应该返回变体,但这在框架代码中或使用需要这种灵活性的框架时最为常见。 std::variant 或 Qt 的 QVariant 值得考虑。
  • 正如我所说,由于我对 C++ 非常陌生,所以我只是想弄清楚什么可以做,什么不能做。它让我对语言有了更好的理解。不过你说的有道理。对于我想用 C++ 做的大多数事情,我可能不需要返回变体的东西。至少我现在绝对不需要它。感谢您的意见。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-09
相关资源
最近更新 更多