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


113

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

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


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

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

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

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


1
オブジェクトメンバー関数を呼び出して、ポインターをオブジェクトに単に渡す場合は、なぜ「なぜ」理解できませんか?クラスをより適切にカプセル化できるので、すべてのクラスが継承するインターフェイスクラスを作成しないのではないかと人々が不満を言うなら、
チャド

多くの人はboost :: functionを使用して生のメンバーポインターのメカニズムを非表示にしますが、コマンドパターンのようなものを実装するのに役立ちます。
CBベイリー

9
なぜその犬を動的に割り当てるのですか?その後、オブジェクトも手動で削除する必要があります。これは、Java、C#、または他の同等の言語を使用していて、まだC ++と戦っているように見えます。単純な自動オブジェクト(Dog dog;)が必要な可能性が高くなります。
sbi 2009

1
@チャド:私はほとんど同意しますが、参照を渡すことがより高価になる場合があります。一部のif / else計算に基づいて関数を呼び出すことができるよりも、指摘された関数を呼び出すだけでそのようなif / thenを回避できるというコストを課すよりも、あるタイプのデータ(構文解析、計算など)を反復するループを検討してください。 / elseは、ループに入る前にこれらのチェックを実行できるかどうかをチェックします。
エリック

1
メンバー関数への関数ポインターも参照してください。
-jww

回答:


126

詳細はこちらをお読みください:

// 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');

23
彼らがこれを決定したのは驚くべきことです:*this.*pt2Memberうまくいくでしょう。*よりも優先順位が高い.*...個人的には、私はまだ書いていただろう、それはthis->*pt2Member1つ少ない演算子です。
Alexis Wilke、2014

7
なぜ初期化pt2ConstMemberする必要があるのNULLですか?
Ciro Santilli郝海东冠状病六四事件法轮功

@AlexisWilkeどうして意外なの?直接オブジェクト(ポインタではない)の場合は(object.*method_pointer)なので、*に優先順位を付けたい。
Ciro Santilli郝海东冠状病六四事件法轮功

@TomášZatoは、私が誤解していない場合(そして私が誤解している可能性があります)は、this適用.*するものはすべて(サブ)クラスのインスタンスへのポインターであることを示すために使用されています。ただし、これは私にとって新しい構文です。ここにリンクされている他の回答とリソースに基づいて推測しているだけです。それをより明確にするために編集を提案します。
c1moore

1
ああスナップ、100おめでとうございます!
ジョナサンミー

57

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

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

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

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

(pDog->*pBark)();

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

私はあなたがこのようなコンストラクタで作業できるとは信じていません-アクターと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*で、通常はa の代わりにa を使用できますがAnimal*、関数ポインターの型はクラス階層のルックアップルールに従いませ。したがって、AnimalメソッドポインターはDogメソッドポインターと互換性がありません。つまりDog* (*)()、型の変数にを割り当てることはできません。Animal* (*)()

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

上記に答えたので、あなたが必要とするものを達成するためのより良い方法はないのでしょうか。この種のことを行う特定のシナリオがいくつかありますが、問題に対してより効果的な他のパターンがある場合があります。達成しようとしていることをより一般的な用語で説明すると、ハイブマインドがさらに役立つ場合があります。

上記に関連して、Boostバインドライブラリと他の関連モジュールが非常に役立つことは間違いありません。


10
私はC ++を10年以上使用しており、定期的に新しいことを学び続けています。今まで聞いたことはありませんでした->*が、今は絶対に必要ないでしょう:)
トーマス

31

誰かがここで1つの問題は " メンバーポインターが必要であることだと説明しましたは、通常の関数ポインターではなく」。

関数へのメンバーポインターは、単なる関数ポインターではありません。実装用語では、コンパイラーは単純な関数アドレスを使用できません。これは、一般に、どのオブジェクトを参照解除するか(仮想関数を考える)がわかるまで、呼び出すアドレスがわからないためです。また、提供するためにオブジェクトを知る必要があります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の方法で見つけることができます。

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

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


「myfunction」は、通常、Cスタイルの関数を意味する場合、標準関数ではありません。'myfunction'は、より正確にはmyclassのメソッドと呼ばれます。クラスのメソッドは、Cスタイルの関数にはない「this」ポインタがあるという点で、通常の関数とは異なります。
Eric

3
ブーストを使用するようアドバイスするのは厳格です。メソッドポインタを使用することには、実際的な理由があります。私はブーストの代わりとしての言及を気にしませんが、誰かがすべての事実を知らずにそれを使用するべきだと誰かが言ったときに嫌いです。ブーストは有料です!そして、これが組み込みプラットフォームである場合、それは可能な選択ではないかもしれません。これを超えて、私はあなたの書き込みが本当に好きです。
エリック、

@エリック-2つ目のポイントでは、「あなたはブーストを使うべきだ」と言うつもりはありませんでした。実際、私はブーストを自分で使用したことがありません。(私が3年後に知っている限り)意図は、人々が代替案を探し、いくつかの可能性を列挙することでした。「または何でも」は、リストが網羅的であることを意図していないことを示します。メンバーポインターは、読みやすさにコストがかかります。それらの簡潔なソース表現は、実行時のコストを隠すこともできます。特に、メソッドへのメンバーポインターは、非仮想メソッドと仮想メソッドの両方に対応し、どちらを知っている必要があります。
Steve314

@Eric-それだけでなく、これらの問題がメンバーポインターとの互換性がない理由です-少なくとも以前は、Visual C ++はメンバーポインター型を表す方法についていくつかの追加の手がかりを必要としていました。私は組み込みシステムに静的関数アプローチを使用します-ポインターの表現は他の関数ポインターと同じで、コストは明白で、移植性の問題はありません。また、静的メンバー関数によってラップされた呼び出しは、呼び出しが仮想かどうか(コンパイル時に)を認識します。仮想メソッドの通常のvtableルックアップ以外の実行時チェックは必要ありません。
Steve314

@Eric-最初の点について-静的メンバー関数はCスタイルの関数(したがって「スコープの問題は別です」)とまったく同じではないことを承知していますが、おそらく名前を含めるべきでした。
Steve314、2012年

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

2
(pDog-> * doSomething)(); // pDogがポインタの場合//(pDog。* doSomething)(); // pDogが()演算子としての参照である場合、-> *と。*が優先されます。
Tomek

12

最小限の実行可能な例

main.cpp

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

コンパイルして実行:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

Ubuntu 18.04でテスト済み。

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

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

GCC 9.2は失敗します:

main.cpp: In function int main()’:
main.cpp:19:18: error: must use ‘.*’ or ‘->*’ to call pointer-to-member function in p (...)’, e.g. ‘(... ->* p) (...)’
   19 |     assert(c.*p(2) == 3);
      |

C ++ 11標準

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

C ++ 11 N3337標準ドラフト

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

11

メソッドから関数ポインター(メソッドポインターではない)を作成する方法を学ぶためにここに来ましたが、ここでの答えのどれも解決策を提供しません。これが私が思いついたものです:

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)

編集:
C ++ 17を使用すると、さらに優れたソリューションがあります。

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

マクロなしで直接使用できます:

Dog dog;
using BarkFunction = void (*)(Dog*);
BarkFunction bark = MethodHelper<&Dog::bark>::call;
(*bark)(&dog); // or simply bark(&dog)

constあなたのような修飾子を持つメソッドの場合、次のようなさらに特殊化が必要になる場合があります

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

6

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

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

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

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


1
私はこれに賛成票を投じましたが、「どのインスタンス」で何を参照しているかがOPにわからない場合に備えて、明確化のポイントを追加します。固有の「this」ポインターを説明するために拡張します。
エリック

6

クラスメンバーへの関数ポインターは、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);
}

2

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


クローンは高価になる可能性があり、パフォーマンスが問題であるか、または懸念がある場合、OPはクローンを回避したい場合があります。
エリック、

0

これをstd :: functionとstd :: bindで行いました。

ハンドラーのベクターをイベントタイプ(const unsigned intであり、名前空間スコープの大きな列挙型)をそのイベントタイプのハンドラーのベクターにマップするunordered_mapにハンドラーのベクターを格納するこのEventManagerクラスを書きました。

EventManagerTestsクラスで、次のようにイベントハンドラーを設定します。

auto delegate = std::bind(&EventManagerTests::OnKeyDown, this, std::placeholders::_1);
event_manager.AddEventListener(kEventKeyDown, delegate);

AddEventListener関数は次のとおりです。

std::vector<EventHandler>::iterator EventManager::AddEventListener(EventType _event_type, EventHandler _handler)
{
    if (listeners_.count(_event_type) == 0) 
    {
        listeners_.emplace(_event_type, new std::vector<EventHandler>());
    }
    std::vector<EventHandler>::iterator it = listeners_[_event_type]->end();
    listeners_[_event_type]->push_back(_handler);       
    return it;
}

以下は、EventHandlerタイプの定義です。

typedef std::function<void(Event *)> EventHandler;

次に、EventManagerTests :: RaiseEventに戻り、これを行います。

Engine::KeyDownEvent event(39);
event_manager.RaiseEvent(1, (Engine::Event*) & event);

EventManager :: RaiseEventのコードは次のとおりです。

void EventManager::RaiseEvent(EventType _event_type, Event * _event)
{
    if (listeners_.count(_event_type) > 0)
    {
        std::vector<EventHandler> * vec = listeners_[_event_type];
        std::for_each(
            begin(*vec), 
            end(*vec), 
            [_event](EventHandler handler) mutable 
            {
                (handler)(_event);
            }
        );
    }
}

これは機能します。EventManagerTests :: OnKeyDownで呼び出しを取得します。クリーンアップの時間になるとベクターを削除する必要がありますが、一度削除するとリークは発生しません。私のコンピューターでイベントを発生させるのに約5マイクロ秒かかります。これは2008年頃です。正確には超高速ではありませんが。私がそれを知っていて、それをウルトラホットコードで使用しない限り、十分に公平です。

私は自分のstd :: functionとstd :: bindをロールバックして高速化したいのですが、おそらくベクトルのunordered_mapではなく配列の配列を使用しますが、メンバー関数を格納する方法がまだわかりませんポインタがあり、呼び出されているクラスについて何も知らないコードから呼び出します。まつげの答えは非常に興味深いようです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.