如何将重写的虚函数的指针发送到基类?

Posted

技术标签:

【中文标题】如何将重写的虚函数的指针发送到基类?【英文标题】:How to send a pointer of an overriden virtual function to the base class? 【发布时间】:2021-04-19 20:49:44 【问题描述】:

一个例子:

class Base

    protected:
        virtual void function()  std::cout << "Base\n"; 
        void callfunction(void (Base::* func)())  (this->*func)(); 
;

class Derived1 : public Base

    public:
        void publicCall()  callfunction(&Derived1::function); 
    private:
        void function() override  std::cout << "Derived1\n"; 
;

我希望能够将派生函数指针发送到基类,以实现可以在基类中指定的模式。因此,重用代码。

但是,我不确定基类中的类型应该是什么,因为 Base::* 不是 Derived1::*。

我收到以下错误: No instance of overloaded function callfunction matches the argument list: arguments types are (Derived1::*)

现在很明显,我可以将数据类型更改为Derived1::*,但这不允许我将其他派生类虚函数发送回Base进行处理,例如Derived2::function()

可以像这样创建模板类型的函数吗?

【问题讨论】:

您的语法已关闭:您需要(this-&gt;*func)()callFunction(&amp;Derived1::function);。但是,是的,即使这样,它也不起作用 谢谢@Justin 我会将语法修复为编辑。我以前也这样写过,但还是没有解决问题。 必须保护您的虚拟功能吗?如果你使用callfunction(&amp;Base::function),它会起作用,但除非虚函数是公共的,否则它不起作用......或者你可以在Base中创建一个受保护的auto getfunction() return &amp;Base::function; 没有。我不想将其公开,因为这些功能仅限于派生类,客户端无法访问。这是一种解决方法,但不是解决方案。 【参考方案1】:

只需发送&amp;Base::function。既然是虚拟的,就会调用正确的。

这里唯一的问题是Base::function 受到保护,您无法从Derived 访问它(奇怪但确实如此)。 OTOH &amp;Derived::function 不会转换为 void (Base::* func)() (并且有充分的理由)。所以你必须要么公开Base::function,要么提供一个访问器:

class Base  
  ...
  protected:
    auto getFunction()  return &Base::function; 

Live demo

【讨论】:

有道理从来没想过。 IMO,这是最好的解决方案。它是安全的(例如,我的答案很容易使用不正确并导致 UB),很明显。它避免了更复杂的语言功能(例如模板)。等等。 这是我的第二选择,但我正在处理一个包含数百个函数的类。使该解决方案更难以实施。 @cwbusacker 将虚拟功能拉到接口中怎么样?例如。 godbolt.org/z/13czdbW85.【参考方案2】:

使用std::function&lt;&gt; 代替C 样式的函数指针。

std::function<returnType (parameterType)> functionPointer = functionName;

不要忘记包含&lt;functional&gt;

我认为这会解决您的问题,但肯定是首选。

直接传递:

void callfunciton(const std::function<void()>& func);

并调用

callfunction([] function(); );

编辑: 阅读this,了解为什么应该使用std::function&lt;&gt;

【讨论】:

刚刚在帖子中修复了这个问题。我试过使用std::function,但由于某种原因它似乎无法识别它。 我想接下来的问题是,如何在使用 std::function 时将函数作为参数发送?我试过使用 &function1 和 &Derived1::function1 并且没有 &。 你是否包含了功能性? 是的,但仅在使用 std::function 的基类中。我还需要在派生类中#include &lt;functional&gt; 吗?【参考方案3】:

您可以将成员函数指针强制转换为基类成员函数指针类型:callfunction(static_cast&lt;void (Base::*)()&gt;(&amp;Derived1::function));。完整示例:

class Base

    protected:
        virtual void function()  std::cout << "Base\n"; 
        void callfunction(void (Base::* func)())  (this->*func)(); 
;

class Derived1 : public Base

    public:
        void publicCall()  callfunction(static_cast<void (Base::*)()>(&Derived1::function)); 
    private:
        void function() override  std::cout << "Derived1\n"; 
;

这是允许的;见cppreference on pointers to member functions:

指向基类成员函数的指针可以隐式转换为指向派生类相同成员函数的指针... 使用static_cast 和显式转换,允许从派生类的成员函数的指针到明确的非虚拟基类的成员函数的指针进行相反方向的转换

在这种情况下,我们将派生转换为基而不是反之,因此我们需要static_cast


您可以通过从模板中强制转换 Base 来避免要求从调用站点进行强制转换:

class Base

    protected:
        virtual void function()  std::cout << "Base\n"; 
        void callfunction(void (Base::* func)())  (this->*func)(); 

        template <typename Derived, std::enable_if_t<std::is_convertible_v<Derived const*, Base const*>, int> = 0>
        void callfunction(void (Derived::* func)())  callfunction(static_cast<void (Base::*)()>(func)); 
;

class Derived1 : public Base

    public:
        void publicCall()  callfunction(&Derived1::function); 
    private:
        void function() override  std::cout << "Derived1\n"; 
;

编译器资源管理器链接:https://godbolt.org/z/3x8qPK4Tf

【讨论】:

我愿意这样做。然而,这个模板函数被 10 个类调用了大约 100 次,这使得它非常繁琐,包括 1000 次强制转换。因此,使用强制转换似乎相当复杂。 这是允许的,但是如果你不小心用非虚拟函数尝试这个转换,你会得到 UB。 我很欣赏编译器资源管理器。这可能是迄今为止最好的答案之一。我遇到的唯一麻烦是有时我想发送 nullptr 或 NULL 作为函数指针,因为在某些情况下我不希望该函数发生在基类中。我希望它跳过它。有解决办法吗?我遇到了无法将 nullptr 转换为 Derived::* @Justin 的问题 @cwbusacker 这似乎对我有用:godbolt.org/z/hY1KhdhMP。您只是在问如何测试 PMF 是否为空?这与其他指针相同; if (func) /* call it */if (func != nullptr) /* call it */ @Justin,适用于基类案例。但是,在我的场景中(试图简化我的问题),我实际上正在向函数发送多个函数指针,其中一些可能是 nullptr,而另一些则不是。我认为 nullptr 不能作为 Derived::* 传递。最好的解决方案是在允许 nullptr 的情况下创建多个实例吗?或者我可以以某种方式更改 std::is_convertible_v() 吗?【参考方案4】:

您的问题是询问 CRTP,这是一个名称不佳但众所周知的标签,用于 Jim Coplien 在 1995 年的一篇题为“奇怪重复的模板模式”的文章中描述的成语。这个想法有据可查,所以我不会深入探讨,但这个想法很简单:基类接受模板参数,派生类将其类型提供给基类​​。然后基类可以将 this 指针“向下转换”为派生类型并调用函数,而无需虚拟调用。

https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

What is the curiously recurring template pattern (CRTP)?

【讨论】:

【参考方案5】:

我认为模板函数应该可以工作

template<class _DType>
void Base::callFunction(void(_DType::*func)())  func(); 

然后是来自Derived1的函数

void Derived1::publicCall()  Base::callFunction(&Derived1::function); 

这项工作只要Derived1 是由Base 派生的,那不是你写的,但我假设你只是错过了,无论如何下次我会添加class Derived1: public Base 以获得更清晰的代码。

【讨论】:

履行我的公民义务:请勿使用前导下划线后跟大写字母来命名标识符。这些名称是保留的,违反此名称会导致未定义的行为。 @ChrisUzdavinis 干什么? func(); 不会编译。 (this-&gt;*func)(); 也不会编译。 (static_cast&lt;_DType*&gt;(this)-&gt;*func)() @CEPB — 它们保留供实施使用。没有什么可以禁止标准库定义一个名为 _DType 的宏。

以上是关于如何将重写的虚函数的指针发送到基类?的主要内容,如果未能解决你的问题,请参考以下文章

自己的真实面试

是否可以通过在基类中添加新的虚函数来破坏代码?

通过基类的指针或引用调用虚函数构成多态

通过基类的指针或引用调用虚函数构成多态

C++中的多态

[C++11]override关键字的使用