133

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

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

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

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

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

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

4

10 に答える 10

149

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

// 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');
于 2009-09-28T10:04:27.077 に答える
63

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

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

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

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

(pDog->*pBark)();

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

このようなコンストラクターを使用できるとは思いません。ctorsとdtorsは特別です。この種のことを実現する通常の方法は、ファクトリメソッドを使用することです。これは基本的に、コンストラクタを呼び出す静的関数です。例については、以下のコードを参照してください。

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

#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を使用できますが、関数ポインターの型はクラス階層のルックアップ規則に従いません。したがって、AnimalメソッドポインタはDogメソッドポインタと互換性がありません。つまり、タイプの変数にを割り当てることはできません。Animal*Dog* (*)()Animal* (*)()

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

上記に答えたので、あなたが必要とするものを達成するためのより良い方法はないのだろうかと思います。この種のことを行う特定のシナリオがいくつかありますが、問題に対してより適切に機能する他のパターンがある場合があります。あなたが達成しようとしていることをより一般的な言葉で説明すると、集合精神はさらに役立つかもしれません!

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

于 2009-09-28T10:20:45.937 に答える
33

ここで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用に設計された古いWin32APIを使用する場合にはかなり一般的です。もちろん、その場合、パラメータは通常、ポインタではなくLPARAMまたは同様のものであり、キャストが必要です。

于 2009-09-28T14:54:11.217 に答える
18
typedef void (Dog::*memfun)();
memfun doSomething = &Dog::bark;
....
(pDog->*doSomething)(); // if pDog is a pointer
// (pDog.*doSomething)(); // if pDog is a reference
于 2009-09-28T08:33:40.760 に答える
15

実行可能な最小限の例

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

Ubuntu18.04でテスト済み。

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

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

GCC9.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「ポインタからメンバーへの演算子」はそれらが何をするかを説明します
于 2015-07-01T09:06:17.943 に答える
12

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

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...);
    }
};
于 2016-06-18T10:15:48.933 に答える
7

クラスメンバーへの関数ポインタは、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);
}
于 2009-09-28T13:18:36.990 に答える
6

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

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

  • 呼び出すメンバー関数
  • どのインスタンスを使用する必要があるか(そのメンバー関数)

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

于 2009-09-28T08:55:27.510 に答える
2

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

于 2009-09-28T09:34:06.373 に答える
2

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

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

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ではなく配列の配列を使用して高速化したいのですが、メンバー関数を格納する方法がよくわかりません。ポインタを作成し、呼び出されているクラスについて何も知らないコードから呼び出します。まつげの答えはとても面白いようです。

于 2020-06-29T20:20:46.990 に答える