web-dev-qa-db-ja.com

関数ポインターを介したC ++クラスメソッドの呼び出し

クラスメンバー関数の関数ポインターを取得し、後でそのメンバー関数を特定のオブジェクトで呼び出すにはどうすればよいですか?書きたい:

class Dog : Animal
{
    Dog ();
    void bark ();
}

…
Dog* pDog = new Dog ();
BarkFunction pBark = &Dog::bark;
(*pBark) (pDog);
…

また、可能であれば、ポインターを使用してコンストラクターも呼び出したいと思います。

NewAnimalFunction pNew = &Dog::Dog;
Animal* pAnimal = (*pNew)();    

これは可能ですか?可能な場合、これを行うための好ましい方法は何ですか?

100
Tony the Pony

詳細については this を読んでください:

// 1 define a function pointer and initialize to NULL

int (TMyClass::*pt2ConstMember)(float, char, char) const = NULL;

// C++

class TMyClass
{
public:
   int DoIt(float a, char b, char c){ cout << "TMyClass::DoIt"<< endl; return a+b+c;};
   int DoMore(float a, char b, char c) const
         { cout << "TMyClass::DoMore" << endl; return a-b+c; };

   /* more of TMyClass */
};
pt2ConstMember = &TMyClass::DoIt; // note: <pt2Member> may also legally point to &DoMore

// Calling Function using Function Pointer

(*this.*pt2ConstMember)(12, 'a', 'b');
110
Satbir

クラスメンバー関数の関数ポインターを取得し、後で特定のオブジェクトでそのメンバー関数を呼び出すにはどうすればよいですか?

typedefから始めるのが最も簡単です。メンバー関数の場合、型宣言にクラス名を追加します。

typedef void(Dog::*BarkFunction)(void);

次に、メソッドを呼び出すには、->*演算子を使用します。

(pDog->*pBark)();

また、可能であれば、ポインターを介してコンストラクターも呼び出したいと思います。これは可能ですか?可能な場合、これを行うための好ましい方法は何ですか?

このようなコンストラクタで作業できるとは思わない-ctorとdtorは特別です。そのようなことを達成するための通常の方法は、ファクトリメソッドを使用することです。ファクトリメソッドは、基本的にコンストラクタを呼び出す静的関数です。例については、以下のコードを参照してください。

基本的にあなたが説明することをするようにコードを修正しました。以下にいくつかの注意事項があります。

#include <iostream>

class Animal
{
public:

    typedef Animal*(*NewAnimalFunction)(void);

    virtual void makeNoise()
    {
        std::cout << "M00f!" << std::endl;
    }
};

class Dog : public Animal
{
public:

    typedef void(Dog::*BarkFunction)(void);

    typedef Dog*(*NewDogFunction)(void);

    Dog () {}

    static Dog* newDog()
    {
        return new Dog;
    }

    virtual void makeNoise ()
    {
        std::cout << "Woof!" << std::endl;
    }
};

int main(int argc, char* argv[])
{
    // Call member function via method pointer
    Dog* pDog = new Dog ();
    Dog::BarkFunction pBark = &Dog::makeNoise;

    (pDog->*pBark)();

    // Construct instance via factory method
    Dog::NewDogFunction pNew = &Dog::newDog;

    Animal* pAnimal = (*pNew)();

    pAnimal->makeNoise();

    return 0;
}

ポリモーフィズムの魔法のおかげで、通常Dog*の代わりにAnimal*を使用できますが、関数ポインターのタイプはnotクラス階層のルックアップルールに従います。したがって、AnimalメソッドポインターはDogメソッドポインターと互換性がありません。つまり、Dog* (*)()Animal* (*)()型の変数に割り当てることはできません。

Static newDogメソッドは、新しいインスタンスを作成して返すだけのファクトリーの簡単な例です。静的関数であるため、通常のtypedef(クラス修飾子なし)があります。

上記に答えましたが、私はあなたが必要なものを達成するより良い方法がないのだろうかと思います。この種のことを行う特定のシナリオがいくつかありますが、問題に適した他のパターンがあるかもしれません。達成しようとしていることをより一般的な用語で説明すると、Hive-mindがさらに役立つことがわかります。

上記に関連して、 Boost bind ライブラリと他の関連モジュールが非常に役立つことは間違いないでしょう。

54
gavinb

ここで、1つの問題は、通常の関数ポインターではなく「 member pointers 」が必要であると説明した人はいないと思います。

関数へのメンバーポインターは、単なる関数ポインターではありません。実装用語では、コンパイラは単純な関数アドレスを使用できません。これは、一般に、どのオブジェクトを逆参照するか(仮想関数を考える)がわかるまで呼び出すアドレスがわからないためです。もちろん、this暗黙的パラメーターを提供するためにオブジェクトを知る必要もあります。

あなたはそれらが必要だと言ったので、今私はあなたが本当にそれらを避ける必要があると言います。真剣に、メンバーポインターは苦痛です。同じ目標を達成するオブジェクト指向のデザインパターンを見たり、boost::functionまたは上記のようなものを使用することは、あなたがその選択をすることを前提として、はるかに正気です。

その関数ポインタを既存のコードに提供しているので、本当に単純な関数ポインタが必要な場合、クラスの静的メンバーとして関数を記述する必要があります。静的メンバー関数はthisを理解しないため、オブジェクトを明示的なパラメーターとして渡す必要があります。関数ポインタを必要とする古いCコードを操作するためのこれらの行に沿って、かつては珍しいイディオムがありました

class myclass
{
  public:
    virtual void myrealmethod () = 0;

    static void myfunction (myclass *p);
}

void myclass::myfunction (myclass *p)
{
  p->myrealmethod ();
}

myfunctionは実際には単なる通常の関数なので(スコープの問題は別として)、関数ポインターは通常のCの方法で見つけることができます。

EDIT-この種のメソッドは、「クラスメソッド」または「静的メンバー関数」と呼ばれます。非メンバー関数との主な違いは、クラスの外部から参照する場合、::スコープ解決演算子を使用してスコープを指定する必要があることです。たとえば、関数ポインターを取得するには、&myclass::myfunctionを使用し、呼び出すにはmyclass::myfunction (arg);を使用します。

この種のことは、元はC++ではなくC向けに設計された古いWin32 APIを使用する場合にかなり一般的です。もちろんその場合、パラメーターは通常、ポインターではなくLPARAMまたは同様のものであり、何らかのキャストが必要です。

31
Steve314
typedef void (Dog::*memfun)();
memfun doSomething = &Dog::bark;
....
(pDog->*doSomething)(); // if pDog is a pointer
// (pDog.*doSomething)(); // if pDog is a reference
18
AraK

メソッドから関数ポインター(メソッドポインターではない)を作成する方法を学ぶためにここに来ましたが、ここでの答えはいずれも解決策を提供しません。だから私はそれについて考え、共有する価値があると思うニースのソリューションを見つけました:

template <class T> struct MethodHelper;
template <class C, class Ret, class... Args> struct MethodHelper<Ret(C::*)(Args...)> {
    using T = Ret (C::*)(Args...);
    template <T m> static Ret call(C* object, Args... args) {
        return (object->*m)(args...);
    }
};

#define METHOD_FP(m) MethodHelper<decltype(m)>::call<m>

したがって、あなたの例では次のようになります:

Dog dog;
using BarkFunction = void (*)(Dog*);
BarkFunction bark = METHOD_FP(&Dog::bark);
(*bark)(&dog); // or simply bark(&dog)
9
eyelash

最小限の実行可能な例

#include <cassert>

class C {
    public:
        int i;
        C(int i) : i(i) {}
        int m(int j) { return this->i + j; }
};

int main() {
    // Get a method pointer.
    int (C::*p)(int) = &C::m;

    // Create a test object.
    C c(1);
    C *cp = &c;

    // Operator .*
    assert((c.*p)(2) == 3);

    // Operator ->*
    assert((cp->*p)(2) == 3);
}

括弧の順序を変更したり、括弧を省略したりすることはできません。以下は機能しません。

c.*p(2)
c.*(p)(2)

C++ 11標準

.*および->*は、この目的のためにC++で導入された単一演算子であり、Cには存在しません。

C++ 11 N3337標準ドラフト

  • 2.13「演算子と句読点」には、.*->*を含むすべての演算子のリストがあります。
  • 5.5「メンバーへのポインタ演算子」は彼らが何をするかを説明します

クラスメンバーへの関数ポインターは、boost :: functionの使用に本当に適した問題です。小さな例:

#include <boost/function.hpp>
#include <iostream>

class Dog 
{
public:
   Dog (int i) : tmp(i) {}
   void bark ()
   {
      std::cout << "woof: " << tmp << std::endl;
   }
private:
   int tmp;
};



int main()
{
   Dog* pDog1 = new Dog (1);
   Dog* pDog2 = new Dog (2);

   //BarkFunction pBark = &Dog::bark;
   boost::function<void (Dog*)> f1 = &Dog::bark;

   f1(pDog1);
   f1(pDog2);
}
6
Benjamin

関数ポインタを使用してメンバー関数を呼び出すことができない理由は、通常の関数ポインタは通常、関数のメモリアドレスにすぎないためです。

メンバー関数を呼び出すには、次の2つのことを知る必要があります。

  • 呼び出すメンバー関数
  • 使用するインスタンス(メンバー関数)

通常の関数ポインタは両方を保存できません。 C++メンバー関数ポインターは、a)の格納に使用されます。そのため、メンバー関数ポインターを呼び出すときにインスタンスを明示的に指定する必要があります。

6
hrnt

新しいオブジェクトを作成するには、前述のように配置newを使用するか、クラスにオブジェクトのコピーを作成するclone()メソッドを実装させることができます。次に、前述のメンバー関数ポインターを使用してこのcloneメソッドを呼び出し、オブジェクトの新しいインスタンスを作成できます。クローンの利点は、オブジェクトの型がわからない基本クラスへのポインタを使用している場合があることです。この場合、clone()メソッドを使用する方が簡単です。また、clone()を使用すると、必要に応じてオブジェクトの状態をコピーできます。

2
Corwin Joy