C++ 继承和成员函数指针
-
09-06-2019 - |
题
在C++中,成员函数指针可以用来指向派生(甚至基)类成员吗?
编辑:也许一个例子会有所帮助。假设我们有一个由三个类组成的层次结构 X
, Y
, Z
按照继承顺序。Y
因此有一个基类 X
和一个派生类 Z
.
现在我们可以定义一个成员函数指针 p
上课 Y
. 。写成:
void (Y::*p)();
(为简单起见,我假设我们只对具有签名的函数感兴趣 void f()
)
这个指针 p
现在可以用来指向类的成员函数 Y
.
这个问题(实际上是两个问题)是:
- 能
p
用于指向派生类中的函数Z
? - 能
p
用于指向基类中的函数X
?
解决方案
C++03 标准, §4.11 2 成员指针转换:
类型为“指向 B 类型成员的指针”的右值 简历 T”,其中 B 是类类型,可以转换为“指向 D 类型成员的指针”类型的右值 简历 T”,其中 D 是 B 的派生类(第 10 条)。如果 B 是 D 的不可访问(第 11 条)、不明确(10.2)或虚拟(10.1)基类,则需要此转换的程序是格式错误的。转换的结果引用与转换发生之前指向成员的指针相同的成员,但它引用基类成员,就好像它是派生类的成员一样。结果引用 D 的 B 实例中的成员。由于结果的类型为“指向类型为 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:
类型为“指向 D 类型成员的指针”的右值 CV1 T”可以转换为“指向 B 类型成员的指针”类型的右值 CV2 T”,其中 B 是基类(子句 10 类派生) D,如果存在从“指向类型 T 的 B 成员的指针”到“指向类型 T 的 D 成员的指针”的有效标准转换(4.11 转换内存), 和 CV2 简历资格与以下相同或更高: CV1.11) 空成员指针值(4.11 转换内存) 转换为目标类型的空成员指针值。如果类 B 包含原始成员,或者是包含原始成员的类的基类或派生类,则生成的指向成员的指针将指向原始成员。否则,转换的结果是未定义的。[笔记:尽管 B 类不需要包含原始成员,但取消引用指向成员的指针的对象的动态类型必须包含原始成员;看 5.5 表达式.mptr.oper.]
11) 函数类型(包括用于成员函数类型的指针中的功能类型)永远不会被CV合并;看 8.3.5 dcl.fct.
简而言之,您可以从派生的转换 D::*
到一个基地 B::*
如果你可以从 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;
}
指向成员的指针的关键问题是它们可以应用于任何指向正确类型的类的引用或指针。这意味着因为 Z
源自 Y
指针(或引用)类型的指针(或引用) Y
实际上可能指向(或引用)的基类子对象 Z
或者 任何其他班级 源自 Y
.
void (Y::*p)() = &Z::z_fn; // illegal
这意味着分配给指向成员的指针的任何内容 Y
实际上必须与任何 Y
. 。如果允许它指向一个成员 Z
(那不是 Y
) 那么就可以调用以下的成员函数 Z
在一些实际上不是的事情上 Z
.
另一方面,任何指向成员的指针 Y
还点了成员 Z
(继承意味着 Z
具有其基类的所有属性和方法)将指针转换为成员是否合法 Y
指向成员的指针 Z
. 。这本质上是安全的。
void (Y::*p)() = &Y::y_fn;
void (Z::*q)() = p; // legal and safe
你可能想看看这篇文章 成员函数指针和最快的 C++ 委托 在某些情况下,简短的答案似乎是肯定的。
我相信是这样。由于函数指针使用签名来标识自身,因此基/派生行为将依赖于您调用它的任何对象。
我的实验揭示了以下内容:警告 - 这可能是未定义的行为。如果有人可以提供明确的参考,那将会很有帮助。
- 这有效,但在将派生成员函数分配给时需要进行强制转换
p
. - 这也有效,但在取消引用时需要额外的转换
p
.
如果我们真的雄心勃勃,我们可以问是否 p
可用于指向不相关类的成员函数。我没有尝试过,但是 快速委托 dagorym 的答案中链接的页面表明这是可能的。
总之,我将尽量避免以这种方式使用成员函数指针。像下面这样的段落不会激发信心:
成员功能指针之间的铸造是一个极为模糊的区域。在C ++的标准化期间,关于您是否应该能够将成员函数指针从一个类或基础类的成员函数指针施放到基础类或派生类的成员函数指针,以及是否可以在无关类之间进行施放。到标准委员会下定决心时,不同的编译器供应商已经做出了实施决定,使他们陷入了对这些问题的不同答案中。[FastDelegate 文章]
假设我们有 class X, class Y : public X, and class Z : public Y
您应该能够将 X、Y 的方法分配给 void (Y::*p)() 类型的指针,但不能将 Z 的方法分配给类型为 void (Y::*p)() 的指针。要了解原因,请考虑以下内容:
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;
}