50

C++ では、メンバー関数ポインターを使用して、派生 (または基本) クラスのメンバーを指すことはできますか?

編集:おそらく例が役立ちます。継承順に 3 つのクラスXY、の階層があるとします。したがって、基本クラスと派生クラスがあります。ZYXZ

pこれで、 classのメンバ関数ポインタを定義できますY。これは次のように書かれています。

void (Y::*p)();

(簡単にするために、署名付きの関数のみに関心があると仮定しますvoid f())

このポインターpを使用して、 class のメンバー関数を指すことができるようになりましたY

この質問(実際には2つの質問)は次のとおりです。

  1. p派生クラスの関数を指すために使用できますZか?
  2. p基本クラスの関数を指すために使用できますXか?
4

8 に答える 8

29

C++03 std、§4.11 2 メンバー変換へのポインター:

「 cv T型の B のメンバーへのポインター」型の右辺値 ( B はクラス型) は、「 cv T 型の D のメンバーへのポインター」型の右辺値 (D は派生クラス)に変換できます( B の 10 節)。 B が D のアクセス不可能 (11 節)、あいまい (10.2) または仮想 (10.1) 基本クラスである場合、この変換を必要とするプログラムは形式が正しくありません。変換の結果は、変換前のメンバーへのポインターと同じメンバーを参照しますが、基底クラスのメンバーを派生クラスのメンバーであるかのように参照します。結果は、B の D のインスタンスのメンバーを参照します。結果の型は「cv型の D のメンバーへのポインター」であるため、T」である場合、D オブジェクトで逆参照できます。結果は、B のメンバーへのポインターが D の B サブオブジェクトで逆参照された場合と同じになります。null メンバー ポインター値は、変換先の型の null メンバー ポインター値に変換されます。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- と同じかそれ以上の cv-qualification である場合資格より、cv1 . 11) null メンバポインタ値 ( 4.11 conv.mem) は、変換先の型の null メンバー ポインター値に変換されます。クラス 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から派生したため、実際には の基底クラス サブオブジェクトまたはから派生した他のクラスを指している (または参照している) 可能性があることを意味します。YYZY

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

これは、 のメンバーへのポインターに割り当てられたものは、Y実際にはどの .xml でも機能する必要があることを意味しますYZのメンバー(のメンバーではなかった)を指すことが許可されている場合、実際には ではないもののYメンバー関数を呼び出すことができます。ZZ

一方、 のメンバーへのポインターは、Yのメンバーも指しますZ(継承とは、そのベースのすべての属性とメソッドを持っていることを意味します) のメンバーへのポインターを のメンバーへのZポインターに変換することは正当です。これは本質的に安全です。YZ

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

この記事メンバー関数ポインターと可能な限り最速の C++ デリゲートを確認することをお勧めします 。短い答えは、場合によってはイエスのようです。

于 2008-09-12T21:35:05.823 に答える
1

私たちが持っていると仮定しますclass X, class Y : public X, and class 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のメソッドの呼び出しを許可します。これにより、誰が何を知っているかを知ることができます。ポインタをキャストすることですべてを機能させることができますが、それは安全ではなく、機能することが保証されていません。

于 2010-04-22T04:01:20.723 に答える
1

そう信じる。関数ポインターは署名を使用してそれ自体を識別するため、基本/派生動作は、呼び出したオブジェクトに依存します。

于 2008-09-12T21:30:00.920 に答える
1

私の実験では、次のことが明らかになりました: 警告 - これは未定義の動作である可能性があります。誰かが決定的な参照を提供できると助かります。

  1. これは機能しましたが、派生メンバー関数を に割り当てるときにキャストが必要でしたp
  2. これも機能しましたが、逆参照するときに追加のキャストが必要でしたp

p本当に野心的であると感じている場合は、無関係なクラスのメンバー関数を指すために使用できるかどうかを尋ねることができます. 私は試しませんでしたが、 dagorym の回答にリンクされているFastDelegateページは、それが可能であることを示唆しています。

結論として、この方法でメンバー関数ポインターを使用しないようにします。次のような文章は自信を刺激しません。

メンバー関数ポインター間のキャストは、非常に曖昧な領域です。C++ の標準化中に、メンバー関数ポインターをあるクラスから基本クラスまたは派生クラスのメンバー関数ポインターにキャストできるかどうか、および無関係なクラス間でキャストできるかどうかについて、多くの議論がありました。標準化委員会が決定を下すまでに、さまざまなコンパイラ ベンダーがすでに実装上の決定を行っていたため、これらの質問に対するさまざまな回答に縛られていました。[ FastDelegate 記事]

于 2009-07-15T10:53:14.430 に答える
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 に答える