7

スーパークラスから継承するクラスによって定義されたコールバックを実行できるようにする必要があります。私はC++に比較的慣れていないので、メンバー関数ポインターの主題は非常に曖昧な領域であるように見えます。

質問への回答や、あらゆる種類のことを議論するランダムなブログ投稿を見てきましたが、ここで私の質問を具体的に扱っているものがあるかどうかはわかりません.

これは、私がやろうとしていることを示す単純なコードの塊です。この例はあまり意味がないかもしれませんが、私が書こうとしているコードに正確に似ています。

class A {
    protected:
    void doSomething(void (A::*someCallback)(int a)) {
        (*this.*someCallback)(1234);
    }
};

class B : public A {
    public:
    void runDoIt() { doSomething(&B::doIt); }
    void runDoSomethingElse() { doSomething(&B::doSomethingElse); }
    protected:
    void doIt(int foo) {
        cout << "Do It! [" << foo << "]\n";
    }
    void doSomethingElse(int foo) {
        cout << "Do Something Else! [" << foo << "]\n";
    }
};

int main(int argc, char *argv[]) {
    B b;
    b.runDoIt();
    b.runDoSomethingElse();
}
4

4 に答える 4

8

問題は、 のメンバー関数が のメンバー関数でBはないことです。AたとえBから派生したとしてもですA。がある場合は、指しているオブジェクトの実際の派生型に関係なく、void (A::*)()任意の で呼び出すことができます。A *

(もちろん、同じ原則がA &too にも適用されます。)

Bから派生しA、からC派生すると仮定しAます。void (B::*)()(たとえば) a を と見なすことができた場合void (A::*)()、次のようなことができます。

A *c=new C;
A *b=new B;
void (A::*f)()=&B::fn;//fn is not defined in A
(c->*f)();

のメンバー関数はB、タイプ のオブジェクトで呼び出されますC。結果はせいぜい予測不可能です。

サンプル コードに基づいて、boost のようなものを使用しないと仮定すると、コールバックをオブジェクトとして構造化する傾向があります。

class Callback {
public:
    virtual ~Callback() {
    }

    virtual Do(int a)=0;
};

次に、コールバックを呼び出す関数は、単純な関数ポインターではなく、これらのオブジェクトのいずれかを受け取ります。

class A {
protected:
    void doSomething(Callback *c) {
        c->Do(1234);
    }
};

その後、呼び出したい派生関数ごとに 1 つのコールバックを持つことができます。たとえば、doIt の場合:

class B:public A {
public:
    void runDoIt() {
        DoItCallback cb(this);
        this->doSomething(&cb);
    }
protected:
    void doIt(int foo) {
        // whatever
    }
private:
    class DoItCallback:public Callback {
    public:
        DoItCallback(B *b):b_(b) {}

        void Do(int a) {
            b_->doIt(a);
        }
    private:
        B *b_;
    };
};

定型文を削減する明白な方法は、メンバー関数ポインターをコールバックに入れることです。これは、派生したコールバックが特定の型のオブジェクトを自由に処理できるためです。これにより、コールバックが型 B のオブジェクトで任意のメンバー関数を呼び出すという点で、コールバックがもう少し一般的になります。

class BCallback:public Callback {
public:
    BCallback(B *obj,void (B::*fn)(int)):obj_(obj),fn_(fn) {}

    void Do(int a) {
        (obj_->*fn_)(a);
    }
private:
    B *obj_;
    void (B::*fn_)(int);
};

これは doIt を次のようにします:

void B::runDoIt() {
    BCallback cb(this,&B::doIt);
    this->doSomething(&cb);
}

これは潜在的に「改善」される可能性がありますが、テンプレート化することで、すべての読者がそのように見えるわけではありません。

template<class T>
class GenericCallback:public Callback {
public:
    GenericCallback(T *obj,void (T::*fn)(int)):obj_(obj),fn_(fn) {}

    void Do(int a) {
        (obj_->*fn_)(a);
    }
private:
    T *obj_;
    void (T::*fn_)(int);
};

これを使用すると、上記の runDoIt 関数は次のようになります。

void B::runDoIt() {
    GenericCallback<B> cb(this,&B::doIt);
    this->doSomething(&cb);
}

(一般的なコールバックは、メンバー関数ポインター自体でテンプレート化することもできますが、これはほとんどの場合、実用的な利点を提供する可能性はほとんどありません。入力が増えるだけです。)

継承を必要としないため、この方法で物事を構造化するとうまくいくことがわかりました。したがって、コールバックされるコードにほとんど制限が課せられません。これは常に良いことだと思います。完全に排除するのが難しい冗長性よりも、それの方が重要であることがわかりました。この例に基づいて、このアプローチが実際に適しているかどうかを判断することは不可能ですが...

于 2009-03-14T01:54:45.093 に答える
7

ブースト ライブラリを使用できる場合は、目前のタスクに boost::function を使用することをお勧めします。

class A {
public:
   void doSomething( boost::function< void ( int ) > callback )
   {
      callback( 5 );
   }
};

次に、任意の継承 (または外部クラス) で boost::bind を使用して呼び出しを行うことができます。

class B {
public:
   void my_method( int a );
};
void test()
{
   B b;
   A a;
   a.doSomething( boost::bind( &B::my_method, &b, _1 ) );
};

正確な構文を確認しておらず、頭のてっぺんから入力しましたが、少なくとも適切なコードに近いものです。

于 2009-03-14T00:26:01.467 に答える
2

C++ は、目的に応じてメンバー関数ポインターよりも優れた構造を提供します。(一般的に言えば、適切に作成された純粋な C++ では関数ポインターを使用する必要はありません)。私の頭の上から2つのオプション:

1) A に doIt() という純粋仮想関数を作成します。doSomething() から doIt() を呼び出します。B では、何をしたいのかを示す列挙メンバー変数を保持し、doSomething() を呼び出す前に設定します。B で doIt() をオーバーライドし、その実装を使用して、メンバー列挙型を実行したい基になるコードに切り替えます。

2) 単一の doIt() 純粋仮想メソッドを持つ DoInterface クラスを作成します。所有する B インスタンスへのポインターを持つクラス B の派生物を作成し、各 DoInterface 実装で B の適切な関数を呼び出す doIt() を実装します。doSomething() は DoInterface のインスタンスをパラメーターとして受け取ります。これは、コールバック オブジェクト パターンと呼ばれます。

于 2009-03-14T00:37:59.210 に答える
0

派生クラスでメソッド基底クラスとして定義されたメソッドを呼び出そうとしています。
しかし、この関数は基本クラスでは定義されていません。

于 2009-03-14T00:34:18.280 に答える