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

发布于 2024-07-05 06:04:51 字数 581 浏览 7 评论 0原文

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

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

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

void (Y::*p)();

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

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

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

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

In C++, can member function pointers be used to point to derived (or even base) class members?

EDIT:
Perhaps an example will help. Suppose we have a hierarchy of three classes X, Y, Z in order of inheritance.
Y therefore has a base class X and a derived class Z.

Now we can define a member function pointer p for class Y. This is written as:

void (Y::*p)();

(For simplicity, I'll assume we're only interested in functions with the signature void f() )

This pointer p can now be used to point to member functions of class Y.

This question (two questions, really) is then:

  1. Can p be used to point to a function in the derived class Z?
  2. Can p be used to point to a function in the base class X?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

夏末染殇 2024-07-12 06:04:51

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

I'm not 100% sure what you are asking, but here is an example that works with virtual functions:

#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;
}
一刻暧昧 2024-07-12 06:04:51

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

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

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

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

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

The critical issue with pointers to members is that they can be applied to any reference or pointer to a class of the correct type. This means that because Z is derived from Y a pointer (or reference) of type pointer (or reference) to Y may actually point (or refer) to the base class sub-object of Z or any other class derived from Y.

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

This means that anything assigned to a pointer to member of Y must actually work with any Y. If it was allowed to point to a member of Z (that wasn't a member of Y) then it would be possible to call a member function of Z on some thing that wasn't actually a Z.

On the other hand, any pointer to member of Y also points the member of Z (inheritance means that Z has all the attributes and methods of its base) is it is legal to convert a pointer to member of Y to a pointer to member of Z. This is inherently safe.

void (Y::*p)() = &Y::y_fn;
void (Z::*q)() = p; // legal and safe
笑看君怀她人 2024-07-12 06:04:51

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

You might want to check out this article Member Function Pointers and the Fastest Possible C++ Delegates The short answer seems to be yes, in some cases.

玩心态 2024-07-12 06:04:51

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

I believe so. Since the function pointer uses the signature to identify itself, the base/derived behavior would rely on whatever object you called it on.

陈独秀 2024-07-12 06:04:51

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

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

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

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

成员函数之间的转换
指针是一个极其模糊的区域。
在 C++ 标准化过程中,
有很多讨论
你是否应该能够投射
来自一个类的成员函数指针
指向基类的成员函数指针
或派生类,以及您是否
可以在不相关的类之间进行转换。
到标准委员会成立时
下定决心,不同的编译器
供应商已经做了
实施决定
将他们锁定在不同的答案中
这些问题。 [FastDelegate 文章]

My experimentation revealed the following: Warning - this might be undefined behaviour. It would be helpful if someone could provide a definitive reference.

  1. This worked, but required a cast when assigning the derived member function to p.
  2. This also worked, but required extra casts when dereferencing p.

If we're feeling really ambitious we could ask if p can be used to point to member functions of unrelated classes. I didn't try it, but the FastDelegate page linked in dagorym's answer suggests it's possible.

In conclusion, I'll try to avoid using member function pointers in this way. Passages like the following don't inspire confidence:

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]

埋情葬爱 2024-07-12 06:04:51

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

Assume that we have class X, class Y : public X, and class Z : public Y

You should be able to assign methods for both X, Y to pointers of type void (Y::*p)() but not methods for Z. To see why consider the following:

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?

By allowing that assignment we permit the invocation of a method for Z on a Y object which could lead to who knows what. You can make it all work by casting the pointers but that is not safe or guaranteed to work.

人间☆小暴躁 2024-07-12 06:04:51

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

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

Here is an example of what works.
You can override a method in derived class, and another method of base class that uses pointer to this overridden method indeed calls the derived class's method.

#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;
}
久伴你 2024-07-12 06:04:51

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

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

52)与指向对象的指针(从指向派生的指针到指向基址的指针)(4.1​​0,第 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 是基类(子句 10 class.driven) D 的,如果存在从“指向类型 T 的 B 成员的指针”到“指向类型 T 的 D 成员的指针”的有效标准转换 (4.11 conv.mem< /a>),并且 cv2cv1 具有相同的 cv 限定,或者比 cv1 更高。11) 空成员指针值 ( 4.11 conv.mem) 转换为目标类型的空成员指针值。 如果类 B 包含原始成员,或者是包含原始成员的类的基类或派生类,则生成的指向成员的指针将指向原始成员。 否则,转换的结果是未定义的。 [注意:虽然B类不需要包含原始成员,但取消引用成员指针的对象的动态类型必须包含原始成员; 请参阅 5.5 expr.mptr.oper.]

11) 函数类型(包括指向成员函数的指针中使用的类型)
类型)从来都不是简历限定的; 请参阅8.3。 5 dcl.fct.

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

C++03 std, §4.11 2 Pointer to member conversions:

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

In short, you can convert a pointer to a member of an accessible, non-virtual base class to a pointer to a member of a derived class as long as the member isn't ambiguous.

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

Conversion in the other direction (via static_cast) is governed by § 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.

In short, you can convert from a derived D::* to a base B::* if you can convert from a B::* to a D::*, though you can only use the B::* on objects that are of type D or are descended from D.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文