C ++继承和成员函数指针

C++ inheritance and member function pointers

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

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

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

1
void (Y::*p)();

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

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

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

  • 是否可以使用p指向派生类Z中的函数?
  • 是否可以使用p指向基类X中的函数?

  • C++ 03 STD,第4.11条:指向成员转换的2指针:

    An rvalue of type"pointer to member of B of type cv T," where B is a class type, can be converted to an rvalue of type"pointer to member of D of type cv T," where D is a derived class (clause 10) of B. If B is an inaccessible (clause 11), ambiguous (10.2) or virtual (10.1) base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion refers to the same member as the pointer to member before the conversion took place, but it refers to the base class member as if it were a member of the derived class. The result refers to the member in D’s instance of B. Since the result has type"pointer to member of D of type cv T," it can be dereferenced with a D object. The result is the same as if the pointer to member of B were dereferenced with the B sub-object of D. The null member pointer value is converted to the null member pointer value of the destination type. 52)

    52)The rule for conversion of pointers to members (from pointer to member of base to pointer to member of derived) appears inverted compared to the rule for pointers to objects (from pointer to derived to pointer to base) (4.10, clause 10). This inversion is necessary to ensure type safety. Note that a pointer to member is not a pointer to object or a pointer to function and the rules for conversions of such pointers do not apply to pointers to members. In particular, a pointer to member cannot be converted to a void*.

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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的管辖:

    An rvalue of type"pointer to member of D of type cv1 T" can be converted to an rvalue of type"pointer to member of B of type cv2 T", where B is a base class (clause 10 class.derived) of D, if a valid standard conversion from"pointer to member of B of type T" to"pointer to member of D of type T" exists (4.11 conv.mem), and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1.11) The null member pointer value (4.11 conv.mem) is converted to the null member pointer value of the destination type. If class B contains the original member, or is a base or derived class of the class containing the original member, the resulting pointer to member points to the original member. Otherwise, the result of the cast is undefined. [Note: although class B need not contain the original member, the dynamic type of the object on which the pointer to member is dereferenced must contain the original member; see 5.5 expr.mptr.oper.]

    11) Function types (including those used in pointer to member function
    types) are never cv-qualified; see 8.3.5 dcl.fct.

    简而言之,如果可以从B::*转换为D::*,则可以从派生的D::*转换为基的B::*,但只能在D类型或D派生的对象上使用B::*


    我不完全确定您的要求,但下面是一个使用虚拟功能的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include <iostream>
    using namespace std;

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

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


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

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

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

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

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

    你可能想看看这篇文章的成员函数指针和最快的C++代表,简短的答案似乎是肯定的,在某些情况下。


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

    您应该能够将x和y的方法赋给void(y::*p)()类型的指针,但不能赋给z类型的方法。要了解为什么要考虑以下内容:

    1
    2
    3
    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方法,这可能导致谁知道什么。你可以通过投射指针使一切正常工作,但这不安全,也不保证能正常工作。


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

  • 这是有效的,但在将派生成员函数赋给p时需要强制转换。
  • 这同样有效,但在取消引用p时需要额外的强制转换。
  • 如果我们真的有雄心壮志,我们可以问一下,是否可以使用p来指向无关类的成员函数。我没有尝试过,但Dagorym答案中链接的FastDelegate页面表明这是可能的。

    总之,我将尝试避免以这种方式使用成员函数指针。像下面这样的段落不能激发信心:

    Casting between member function
    pointers is an extremely murky area.
    During the standardization of C++,
    there was a lot of discussion about
    whether you should be able to cast a
    member function pointer from one class
    to a member function pointer of a base
    or derived class, and whether you
    could cast between unrelated classes.
    By the time the standards committee
    made up their mind, different compiler
    vendors had already made
    implementation decisions which had
    locked them into different answers to
    these questions. [FastDelegate article]


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


    下面是一个什么有效的例子。可以重写派生类中的一个方法,并且使用指向此重写方法的指针的基类的另一个方法确实调用了派生类的方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #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;
    }