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

时间:2020-03-05 18:52:32  来源:igfitidea点击:

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

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

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

void (Y::*p)();

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

现在,该指针" p"可用于指向类" Y"的成员函数。

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

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

解决方案

回答

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

回答

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

回答

我不确定我们要问什么,但以下是适用于虚函数的示例:

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

回答

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

  • 这可行,但是在将派生成员函数分配给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]

回答

假设我们有"类X,类Y:公共X,类Z:公共Y"

我们应该能够将X和Y的方法都分配给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的方法,这可能导致谁知道什么。我们可以通过强制转换指针来使其全部正常工作,但这并不安全或者不能保证正常工作。

回答

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*.

简而言之,可以将指向可访问的非虚拟基类的成员的指针转换为指向派生类的成员的指针,只要该成员没有歧义即可。

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 :: *"。

回答

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

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

这意味着分配给指向" Y"成员的指针的任何内容实际上都必须与任何" Y"一起使用。如果允许它指向Z的成员(不是Y的成员),则可以在实际上不是Z的某事上调用Z的成员函数。 `。

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

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