C ++inheritance和成员函数指针

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

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

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

 void (Y::*p)(); 

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

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

这个问题(真的有两个问题)是:

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

C ++ 03 std, §4.112成员转换指针 :

可以将types“ cv T的B成员的指针”(其中B是类types)的右值转换为types“指向ctypesT的D的成员的指针”的右值,其中D是派生类(如果B是不可访问的(第11章),D是不明确的(10.2)或虚拟的(10.1)基类,则需要这种转换的程序是不合格的。 转换的结果是指在转换发生之前指向成员的指针相同的成员,但它指向基类成员,就好像它是派生类的成员一样。 结果指的是D的B实例中的成员。由于结果具有types“指向CtypesT的D成员的指针”,因此可以用D对象取消引用。 结果与将B的成员的指针与D的B子对象解引用的结果相同。将空成员指针值转换为目标types的空成员指针值。 52)

52)与指向对象的指针(从指针到派生指针到基指针)的规则(4.10,第10条)相比,指向成员的指针转换规则(从指针到基本成员到指向派生成员的指针) 。 这种反转对于确保types安全是必要的。 请注意,指向成员的指针不是指向对象的指针或指向函数的指针,这些指针的转换规则不适用于指向成员的指针。 特别是,指向成员的指针不能转换为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:

可以将“typescv1 T的D的成员的指针”types的右值转换为types“指向typescv2 T的B成员的types的指针”的右值,其中B是D的基类(从句10的class.derived )如果存在从“指向Ttypes的B成员的指针”到“T型态成员的指针”( 4.11 conv.mem )的有效标准转换,并且cv2与cv-资格比, cv111)空成员指针值( 4.11 conv.mem )被转换为目标types的空成员指针值。 如果B类包含原始成员,或者是包含原始成员的类的基类或派生类,则生成的指向成员的指针指向原始成员。 否则,转换的结果是不确定的。 [注意:虽然类B不需要包含原始成员,但是指向成员的指针所对象的dynamictypes必须包含原始成员; 见5.5 expr.mptr.oper 。]

11)函数types(包括在成员函数types指针中使用的types)永远不会被cv限定; 见8.3.5 dcl.fct 。

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

我不是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; } 

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

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

这意味着任何分配给Y成员的指针都必须与任何Y工作。 如果允许指向Z (不是Y的成员)的成员,那么可以在某个不是Z东西上调用Z的成员函数。

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

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

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

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

我的实验显示如下:警告 – 这可能是未定义的行为。 如果有人能够提供明确的参考资料将会有所帮助。

  1. 这工作,但派生的成员函数分配给p时需要一个强制转换。
  2. 这也工作,但需要额外的时间解引用p

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

总之,我会尽量避免使用成员函数指针。 像下面这样的通道不能激发自信心:

成员函数指针之间的转换是一个非常模糊的区域。 在C ++的标准化过程中,关于是否应该能够将一个成员函数指针从一个类转换为基类或派生类的成员函数指针,以及是否可以在不相关的类之间进行转换,还存在很多讨论。 在标准委员会下定决心的时候,不同的编译器厂商已经做出了实现的决定,这些决定将这些问题locking在不同的答案中。 [ FastDelegate文章 ]

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

您应该能够将X,Y的方法分配给types为void(Y :: * p)()的指针,但不能为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方法,这可能导致谁知道什么。 你可以通过投射这些指针来完成所有的工作,但这并不安全或者不能保证工作。

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

 #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; }