【问题标题】:C++ inheritance and member function pointersC++ 继承和成员函数指针
【发布时间】:2010-09-08 18:26:48
【问题描述】:

在 C++ 中,成员函数指针可以用来指向派生(甚至是基)类成员吗?

编辑: 也许一个例子会有所帮助。假设我们有三个类的层次结构XYZ,按照继承顺序。 因此Y 有一个基类X 和一个派生类Z

现在我们可以为类Y定义一个成员函数指针p。这写成:

void (Y::*p)();

(为简单起见,我假设我们只对签名为 void f() 的函数感兴趣)

这个指针p现在可以用来指向类Y的成员函数。

那么这个问题(实际上是两个问题)是:

  1. p可以用来指向派生类Z中的函数吗?
  2. p可以用来指向基类X中的函数吗?

【问题讨论】:

  • 你为什么要这样做?
  • 我同意马特的观点。你的问题需要澄清。您是否只是想知道成员函数指针是否与虚函数一起使用?如果是这样,那么答案是肯定的。如果您想问其他问题,请澄清。
  • 我认为这很清楚。 +1 和 smh:您需要进一步解释吗?请指出是不是这样。

标签: c++ inheritance oop member-function-pointers


【解决方案1】:

C++03 标准,§4.11 2 Pointer to member conversions:

类型为“指向cv T的成员的指针”类型的右值,其中B是类类型,可以转换为“指向类型为cv T”,其中 D 是 B 的派生类(第 10 条)。如果 B 是 D 的不可访问(第 11 条)、模糊 (10.2) 或虚拟 (10.1) 基类,则程序需要这种转换是不正确的。转换的结果与发生转换之前的成员指针引用相同的成员,但它引用基类成员,就好像它是派生类的成员一样。结果引用了 D 的 B 实例中的成员。由于结果的类型为“指向类型为 cv T 的 D 成员的指针”,因此可以使用 D 对象取消引用。结果与使用 D 的 B 子对象取消引用 B 的成员的指针相同。空成员指针值转换为目标类型的空成员指针值。 52)

52)与指向对象的指针(从指针到派生到指向基址的指针)(4.1​​0,第 10 条)。这种反转是确保类型安全所必需的。请注意,指向成员的指针不是指向对象的指针或指向函数的指针,并且此类指针的转换规则不适用于指向成员的指针。特别是,指向成员的指针不能转换为 void*。

简而言之,您可以将指向可访问的非虚拟基类成员的指针转换为指向派生类成员的指针,只要该成员不模糊即可。

class A {
public: 
    void foo();
};
class B : public A {};
class C {
public:
    void bar();
};
class D {
public:
    void baz();
};
class E : public A, public B, private C, public virtual D {
public: 
    typedef void (E::*member)();
};
class F:public E {
public:
    void bam();
};
...
int main() {
   E::member mbr;
   mbr = &A::foo; // invalid: ambiguous; E's A or B's A?
   mbr = &C::bar; // invalid: C is private 
   mbr = &D::baz; // invalid: D is virtual
   mbr = &F::bam; // invalid: conversion isn't defined by the standard
   ...

另一个方向的转换(通过static_cast)由§ 5.2.9 9 控制:

“指向cv1 T 类型的D 成员的指针”类型的右值可以转换为“指向cv2 T 类型的B 成员的指针”类型的右值",其中 B 是 D 的基类(子句 10 class.derived),如果存在从“指向 T 类型 B 成员的指针”到“指向 T 类型 D 成员的指针”的有效标准转换 (4.11 conv.mem) ,并且 cv2 的 cv 限定与 cv1 相同或更高。11) 空成员指针值 (@ 987654325@) 转换为目标类型的空成员指针值。如果类 B 包含原始成员,或者是包含原始成员的类的基类或派生类,则指向成员的结果指针指向原始成员。否则,强制转换的结果是未定义的。 [注意:虽然B类不需要包含原始成员,但解除引用成员指针的对象的动态类型必须包含原始成员;见5.5 expr.mptr.oper。]

11) 函数类型(包括用于指向成员函数的指针) types) 永远不是 cv 限定的;见8.3.5 dcl.fct

简而言之,如果您可以从B::* 转换为D::*,您可以从派生的D::* 转换为基础B::*,但您只能在属于的对象上使用B::*类型 D 或者是 D 的后裔。

【讨论】:

  • +1。在stackoverflow.com/questions/4295117/… 他们发现5.2.9/9 说static_cast 可以将T D::* 转换为T B::*,当然这要求你要么(a)知道不要通过它调用不存在的方法;或 (b) 知道 B 对象的动态类型实际上是 D 或派生自它的东西。
  • "简而言之,如果您可以从B::* 转换为D::*,则可以从派生的D::* 转换为基础B::*,尽管您只能使用@987654346 @ 在类型为 D 或从 D 继承的对象上。” - 错误的。如果D::* 指向的成员不包含在B(或其基类)中,则已经将指针转换为B::* 会产生未定义的行为。
  • @j_kubik:不符合标准;如引用中所述,“如果 B 类 [...] 是包含原始成员的类的基 [...] 类,则指向成员的结果指针指向原始成员。”当我第一次阅读它时,这也让我感到惊讶。另外:“注意:虽然 B 类不需要包含原始成员,但取消引用指向成员的指针的对象的动态类型必须包含原始成员。”我的最后一行仍然成立。
  • 好吧,如果 B 没有虚拟方法,但 D 有呢?然后,对于某些编译器,指向B 方法的指针将具有与D 不同的大小(通常更小)。由于这些额外的字节是出于某种目的添加的(为什么?),指向B::* 的缩减指针将无法正确表示D 的方法,即使对象的动态类型是D
【解决方案2】:

我不能 100% 确定您在问什么,但这里有一个适用于虚函数的示例:

#include <iostream>
using namespace std;

class A { 
public:
    virtual void foo() { cout << "A::foo\n"; }
};
class B : public A {
public:
    virtual void foo() { cout << "B::foo\n"; }
};

int main()
{
    void (A::*bar)() = &A::foo;
    (A().*bar)();
    (B().*bar)();
    return 0;
}

【讨论】:

    【解决方案3】:

    指向成员的指针的关键问题是它们可以应用于任何引用或指向正确类型的类的指针。这意味着因为Z 是从Y 派生的,所以指向Y 的指针(或引用)类型的指针(或引用)实际上可能指向(或引用)Z 的基类子对象或任何其他类派生自Y

    void (Y::*p)() = &Z::z_fn; // illegal
    

    这意味着分配给指向Y 成员的指针的任何内容都必须实际与任何Y 一起使用。如果允许它指向Z 的成员(不是Y 的成员),那么就可以在实际上不是@987654332 的东西上调用Z 的成员函数@。

    另一方面,任何指向Y 成员的指针也指向Z 的成员(继承意味着Z 具有其基类的所有属性和方法)是否可以将指针转换为Y 的成员指向 Z 的成员的指针。这本质上是安全的。

    void (Y::*p)() = &Y::y_fn;
    void (Z::*q)() = p; // legal and safe
    

    【讨论】:

      【解决方案4】:

      您可能想查看这篇文章Member Function Pointers and the Fastest Possible C++ Delegates 简短的回答似乎是肯定的,在某些情况下。

      【讨论】:

        【解决方案5】:

        我相信是的。由于函数指针使用签名来标识自己,因此基本/派生行为将依赖于您调用它的任何对象。

        【讨论】:

          【解决方案6】:

          我的实验揭示了以下内容:警告 - 这可能是未定义的行为。如果有人可以提供明确的参考,那将很有帮助。

          1. 这可行,但在将派生成员函数分配给 p 时需要强制转换。
          2. 这也有效,但在取消引用 p 时需要额外的强制转换。

          如果我们真的很有野心,我们可以问一下p 是否可以用来指向不相关类的成员函数。我没有尝试过,但在 dagorym 的答案中链接的 FastDelegate 页面表明这是可能的。

          最后,我会尽量避免以这种方式使用成员函数指针。像下面这样的段落不能激发信心:

          成员函数之间的强制转换 指针是一个非常模糊的区域。 在 C++ 的标准化过程中, 有很多关于 你是否应该能够投 一个类的成员函数指针 到基的成员函数指针 或派生类,以及您是否 可以在不相关的类之间进行转换。 到时候标准委员会 下定决心,不同的编译器 供应商已经做了 执行决定 将他们锁定在不同的答案中 这些问题。 [FastDelegate article]

          【讨论】:

            【解决方案7】:

            假设我们有class X, class Y : public X, and class Z : public Y

            您应该能够将 X、Y 的方法分配给 void (Y::*p)() 类型的指针,但不能将 Z 的方法分配给 Z。要了解原因,请考虑以下几点:

            void (Y::*p)() = &Z::func; // we pretend this is legal
            Y * y = new Y; // clearly legal
            (y->*p)(); // okay, follows the rules, but what would this mean?
            

            通过允许该赋值,我们允许在 Y 对象上调用 Z 的方法,这可能导致谁知道什么。您可以通过转换指针来使其全部工作,但这并不安全或不能保证工作。

            【讨论】:

            • 你的意思是最后一行的(y-&gt;*p)();吗?
            • @j_random_hacker,可能。我花了太多时间在语法不疯狂的语言上。
            【解决方案8】:

            这是一个有效的例子。 您可以在派生类中重写一个方法,而基类的另一个方法使用指向该重写方法的指针确实调用了派生类的方法。

            #include <iostream>
            #include <string>
            
            using namespace std;
            
            class A {
            public:
                virtual void traverse(string arg) {
                    find(&A::visit, arg);
                }
            
            protected:
                virtual void find(void (A::*method)(string arg),  string arg) {
                    (this->*method)(arg);
                }
            
                virtual void visit(string arg) {
                    cout << "A::visit, arg:" << arg << endl;
                }
            };
            
            class B : public A {
            protected:
                virtual void visit(string arg) {
                    cout << "B::visit, arg:" << arg << endl;
                }
            };
            
            int main()
            {
                A a;
                B b;
                a.traverse("one");
                b.traverse("two");
                return 0;
            }
            

            【讨论】:

              猜你喜欢
              • 2012-04-12
              • 2016-06-24
              • 1970-01-01
              • 1970-01-01
              • 2018-09-12
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多