向下转换指向成员函数的指针

Posted

技术标签:

【中文标题】向下转换指向成员函数的指针【英文标题】:Downcasting a pointer to member function 【发布时间】:2016-11-03 08:31:16 【问题描述】:

我想向下转换一个指向成员函数的指针,其中基类是虚拟继承的。这会导致编译时出错。

在我的应用程序中,我有几个对象的集合(例如D1D2),它们有一个共同的基类B。它们通过std::vector<B*> 保持在一起并进行跟踪。

派生对象引入了需要定期调用的double-returning 函数。基类通过std::vector<double(B::*)(void)> 跟踪这些函数。整个链用在 main 函数的倒数第二行。

因此,在将函数指针添加到向量时,我需要转换函数指针,就像在派生类 D1D2 的构造函数中所做的那样。他在D1 的情况下是成功的,但在D2 的情况下却不是,因为BD2 的虚拟基础。据我所知,在这种情况下静态转换不起作用(错误消息 1),因为派生类 D21D22 只有一个指向 B 实例的指针。但是,dynamic_cast 也会导致编译时出错(错误消息 2),表示目标不是指针类型。

错误信息 1:错误:通过虚拟基 'B' 指向成员转换的指​​针 v.push_back(static_cast(&D2::f2));

错误消息 2:错误:不能 dynamic_cast '&D2::f2'(类型为 'double (class D2::)()')到类型 'double(class B: :)()'(目标不是指针或引用)

我想知道如何(如果有的话)执行所需的演员表。如果不是,我想知道为什么这样的事情是不可能的。

编辑:一些背景:我对常微分方程系统进行数值积分。派生类代表系统的各个方程,每个派生类计算自己的状态。通过收集向量o 中的类,我可以快速修改方程组。基类中的向量v 用于输出目的。在集成期间,每个子系统还通过基类处理自己的输出。钻石的出现是因为我将两段常用的代码分成了两个单独的类。

可以说,还有其他方法可以设计类层次结构,还有其他方法可以实现我的目标(在这种情况下,我将B 中的函数p 设为虚拟,并在派生类中重新实现它)。但是,我无法理解演员阵容失败的原因,所以我提出了这个问题。

MWE:

    #include <vector>
    #include <algorithm>
    #include <iostream>

    // Base class
    class B 
    protected:
      std::vector<double(B::*)(void)> v;
    public:
      void p ( void )
      for_each(v.begin(), v.end(), [this] (double (B::*f) (void)) std::cerr << (this->*f)() << std::endl;);;
    ;

    // Normal inheritance
    class D1: public B
    public:
      double f1 ( void )  return 1; 
      D1()  v.push_back(static_cast<double(B::*)(void)>(&D1::f1)); // Casting is successful here.
    ;

    // Setting up 'the diamond'
    class D21: virtual public B ;
    class D22: virtual public B ;
    class D2: public D21, public D22
    public:
      double f2 ( void )  return 2; 
      D2()  v.push_back(dynamic_cast<double(B::*)(void)>(&D2::f2));  // How to cast here?
    ;

    int main ()
    
      // Vector holding the classes together
      std::vector<B*> o;

      // Set up the system
      D1 d1;
      D2 d2;
      o.push_back(&d1);
      o.push_back(&d2);

      // Derived functions are called
      for_each(o.begin(),o.end(),[] (B *o) o->p(););

      return 0;
    

【问题讨论】:

你能详细说明一下为什么你想做这样的事情吗? 原来的问题是什么?您想通过这样做解决什么用例? (Related reading about the XY problem) When should static_cast, dynamic_cast, const_cast and reinterpret_cast be used?的可能重复 dynamic_cast 不能很好地处理“菱形”继承模式。使用reinterpret_cast(这是“我知道我在做什么”演员表)应该可以解决问题。 我看了here的答案,仍然不知道为什么我不能使用dynamic_cast,因为我使用虚拟继承,我害怕使用reinterpret_cast。这在 MWE 中有效,但我昨天在完整的应用程序中尝试过,但没有编译。现在我觉得如果我使用它,并且它有效,它一定会失败。 【参考方案1】:

我相信应该可以实现您尝试使用的演员表。但是,这是一个特别困难的极端情况,这可能是不允许这样做的原因:

您可以将成员指针视为对象(非虚拟情况)或 vtable(虚拟情况)的偏移量。只要对象的相关布局是固定的,这就可以正常工作。然而,对于虚基,虚基在对象中的位置可能取决于具体的子类。更具体地说,从D2 派生的类的虚拟基址可能与D2 本身的偏移量不同。

现在,当您将成员指针转换为相对于其基类之一的指针时,需要调整指针。这通常是相对偏移量的简单相加,这是一个编译时间常数。但由于D2 中虚拟基址的位置在编译时是未知的,因此调整需要首先动态发现这个偏移量。

正如我所说,这并非不可能实现。然而,它增加了编译器中指针调整代码的显着复杂性,只是为了支持极其罕见的极端情况。我想这就是不允许这样做的原因。

【讨论】:

谢谢!我可以接受这样的解释,即实施起来过于复杂。让我感到困惑的是,就第二条错误消息而言,我认为“指针”和“指向成员函数的指针”是同一件事。在将此答案标记为已接受之前,我会再等一会儿。 是否应该将这个答案的要点包含在这个related question 的答案中,也许通过澄清“任何多态类型”不包括指向成员函数的指针? @SerJothanChanes 我不这么认为。这个问题足以对不同类型的演员表进行分类,它不会将那里的读者与 no 演员表起作用的特殊极端情况混淆。这是此问答格式的优点之一:每个答案都可以准确包含回答手头问题所需的详细信息量。您的问题比您链接的相关问题要求更深入的细节。与解释不同类型演员表之间差异无关的细节。

以上是关于向下转换指向成员函数的指针的主要内容,如果未能解决你的问题,请参考以下文章

尝试使用指向成员函数的指针时出现强制转换问题

指向虚拟成员函数的指针是不是具有可比性?

将成员函数指针转换为 FreePascal 中的函数指针

存储指向成员函数的指针不适用于 clang++

用于成员函数指针往返转换的 void(*)() 类似物

C++|详解类成员指针:数据成员指针和成员函数指针及应用场合