50

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

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

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

void (Y::*p)();

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

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

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

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

8 回答 8

29

C++03 标准,§4.11 2 指向成员转换的指​​针:

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

52)与指向对象的指针规则(从派生指针到基址指针)相比,成员指针转换规则(从基址成员指针到派生成员指针)似乎相反(4.10,第 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-qualification 相同或更高资格比,cv111)空成员指针值(4.11 conv.mem) 转换为目标类型的空成员指针值。如果类 B 包含原始成员,或者是包含原始成员的类的基类或派生类,则指向成员的结果指针指向原始成员。否则,强制转换的结果是未定义的。[注意:虽然B类不需要包含原始成员,但解除引用成员指针的对象的动态类型必须包含原始成员;见5.5 expr.mptr.oper。]

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

简而言之,如果可以从 a 转换为 a ,则可以从派生转换为基D::*,尽管您只能使用D 类型或从 D 派生的 on 对象。B::*B::*D::*B::*

于 2010-04-22T06:25:21.923 回答
12

我不是 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;
}
于 2008-09-12T21:42:07.803 回答
7

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

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

这意味着分配给指向成员的指针的任何内容都Y必须实际与 any 一起使用Y。如果允许它指向一个成员Z(不是 的成员Y),那么就有可能Z在一些实际上不是Z.

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

void (Y::*p)() = &Y::y_fn;
void (Z::*q)() = p; // legal and safe
于 2010-04-22T06:58:06.120 回答
3

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

于 2008-09-12T21:35:05.823 回答
1

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

于 2008-09-12T21:30:00.920 回答
1

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

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

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

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

成员函数指针之间的转换是一个非常模糊的区域。在 C++ 的标准化过程中,有很多关于是否应该能够将成员函数指针从一个类强制转换为基类或派生类的成员函数指针,以及是否可以在不相关的类之间强制转换的讨论。当标准委员会做出决定时,不同的编译器供应商已经做出了实施决策,这些决策将他们锁定在对这些问题的不同答案中。[ FastDelegate 文章]

于 2009-07-15T10:53:14.430 回答
1

假设我们有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 的方法,这可能导致谁知道什么。您可以通过转换指针来使其全部工作,但这并不安全或不能保证工作。

于 2010-04-22T04:01:20.723 回答
0

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

#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;
}
于 2015-02-13T18:51:28.733 回答