自由関数が期待される場所でメンバー関数を渡すにはどうすればよいですか?


122

問題は次のとおりです。このコードを検討してください。

#include <iostream>


class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

void function1(void (*function)(int, int))
{
    function(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a();

    function1(&test);
    function1(&aClass::aTest); // <-- How should I point to a's aClass::test function?

    return 0;
}

どのようにして使用することができるaのをaClass::test引数としてfunction1?私はこれを行うことに行き詰まっています。

クラスのメンバーにアクセスしたいのですが。



16
これは完全に重複ではありません(少なくとも、リンクされている特定の質問ではありません)。その質問は、関数へのポインターであるメンバーを宣言する方法についてです。これは、非静的メンバー関数へのポインターをパラメーターとして渡す方法についてです。
CarLuva 14

回答:


144

関数ポインタの使用に問題はありません。ただし、非静的メンバー関数へのポインターは、通常の関数ポインターとは異なります。メンバー関数は、関数に暗黙的な引数として渡されるオブジェクトで呼び出す必要があります。したがって、上記のメンバー関数の署名は

void (aClass::*)(int, int)

使用しようとするタイプではなく

void (*)(int, int)

1つの方法は、メンバー関数を作成することstaticです。この場合、オブジェクトを呼び出す必要がなく、typeで使用できますvoid (*)(int, int)

あなたのクラスの任意の非静的メンバーにアクセスする必要がある場合 、あなたは関数ポインタに固執する必要があり、例えば、関数がCインタフェースの一部であるため、あなたの最良のオプションは、常に通過させることであるvoid*関数ポインタとコールを取るあなたの機能にからオブジェクトを取得しvoid*てメンバー関数を呼び出す転送関数を介してメンバー。

適切なC ++インターフェースでは、関数が任意のクラス型を使用するために、関数オブジェクトのテンプレート化された引数を関数に持たせることを検討したい場合があります。テンプレート化されたインターフェースを使用することが望ましくない場合は、次のようなものを使用するstd::function<void(int, int)>必要がありますstd::bind()。たとえば、を使用して、これらに適切に呼び出し可能な関数オブジェクトを作成できます。

クラス型または適切な型のテンプレート引数を使用するタイプセーフなアプローチは、不適切な型へのキャストによるエラーの可能性を取り除くため、インターフェースをstd::function<...>使用するよりも望ましい方法void*です。

関数ポインターを使用してメンバー関数を呼び出す方法を明確にするために、以下に例を示します。

// the function using the function pointers:
void somefunction(void (*fptr)(void*, int, int), void* context) {
    fptr(context, 17, 42);
}

void non_member(void*, int i0, int i1) {
    std::cout << "I don't need any context! i0=" << i0 << " i1=" << i1 << "\n";
}

struct foo {
    void member(int i0, int i1) {
        std::cout << "member function: this=" << this << " i0=" << i0 << " i1=" << i1 << "\n";
    }
};

void forwarder(void* context, int i0, int i1) {
    static_cast<foo*>(context)->member(i0, i1);
}

int main() {
    somefunction(&non_member, 0);
    foo object;
    somefunction(&forwarder, &object);
}

わかりました、私はこの答えが好きです!「void *からオブジェクトを取得してメンバー関数を呼び出す転送関数を通じてメンバーを呼び出す」とはどういう意味ですか、それへの便利なリンクを共有していただけますか?ありがとう
Jorge Leitao

わかったと思います。(私はあなたの投稿を編集しました)説明と例をありがとう、本当に役に立ちました。確認するだけです。私が指摘したいすべてのメンバー機能について、フォワーダーを作成する必要があります。正しい?
ホルヘレイタオ2012

ええ、そうです。テンプレートの使用効率に応じて、さまざまなクラスやメンバー関数で機能する転送テンプレートを作成できます。これを行う方法は別の機能になると思います;-)
DietmarKühl2012

1
この回答はを使用しているため、嫌いvoid*です。つまり、型チェックが行われていないため、非常に厄介なバグが発生する可能性があります。
Superlokkus

@Superlokkus:代替案で私たちを啓発していただけますか?
DietmarKühl

88

@ピートベッカーの答えは結構ですが、classインスタンスをfunction1C ++ 11の明示的なパラメータとして渡さなくても行うことができます:

#include <functional>
using namespace std::placeholders;

void function1(std::function<void(int, int)> fun)
{
    fun(1, 1);
}

int main (int argc, const char * argv[])
{
   ...

   aClass a;
   auto fp = std::bind(&aClass::test, a, _1, _2);
   function1(fp);

   return 0;
}

1
あるvoid function1(std::function<void(int, int)>)正しいですか?
Deqing

2
関数の引数に変数名を指定する必要があります。変数名は実際に渡すものです。だから:void function1(std::function<void(int, int)> functionToCall)そしてfunctionToCall(1,1);。回答を編集しようとしましたが、なんらかの理由で意味がなさそうだと誰かが拒否しました。ある時点で賛成票が出されるかどうかを確認します。
Dorkyエンジニア

1
@DorkyEngineerかなり変だと思いますが、あなたは正しいと思いますが、そのエラーがいつまで気付かれなかったのかはわかりません。とにかく、私は今答えを編集しました。
Matt Phillips、

2
std :: functionによりパフォーマンスが大幅に低下するというこの投稿を見つけました。
ケビン2015年

2
@kevin、その投稿の回答がベンチマークの欠陥を示したので、コメントを修正する必要があるかもしれません。
ホルヘレイタオ

54

メンバー関数へのポインターは、関数へのポインターとは異なります。ポインターを通じてメンバー関数を使用するには、そのメンバーへのポインター(明らかに)とそれを適用するオブジェクトが必要です。適切なバージョンのはそうfunction1だろう

void function1(void (aClass::*function)(int, int), aClass& a) {
    (a.*function)(1, 1);
}

それを呼び出すには:

aClass a; // note: no parentheses; with parentheses it's a function declaration
function1(&aClass::test, a);

1
どうもありがとうございました。角かっこがここでカウントされることがわかりました:function1(&(aClass :: test)、a)MSVC2013では機能しますが、gccでは機能しません。gccは&をクラス名の前に直接必要とします(&演算子はクラスではなく関数のアドレスを
取得する

のタイプなので、でタイプだと思いfunctionましvoid (aClass::*function)(int, int)typedef void (aClass::*function)(int, int)
Olumide

@Olumide — typedef int X;タイプを定義します。int X;オブジェクトを作成します。
ピートベッカー

11

2011年以降、変更できる場合はfunction1、次のように変更してください。

#include <functional>
#include <cstdio>

using namespace std;

class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

template <typename Callable>
void function1(Callable f)
{
    f(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main()
{
    aClass obj;

    // Free function
    function1(&test);

    // Bound member function
    using namespace std::placeholders;
    function1(std::bind(&aClass::aTest, obj, _1, _2));

    // Lambda
    function1([&](int a, int b) {
        obj.aTest(a, b);
    });
}

ライブデモ

また、壊れたオブジェクト定義を修正したことにも注意してください(aClass a();関数を宣言しています)。


2

私は同様の質問をしました(他のクラスからvoidを渡すC ++ openframeworks)が私が見つけた答えはより明確だったので、ここで将来の記録の説明:

次のようにstd :: functionを使用する方が簡単です。

 void draw(int grid, std::function<void()> element)

そして次のように呼び出します:

 grid.draw(12, std::bind(&BarrettaClass::draw, a, std::placeholders::_1));

またはさらに簡単:

  grid.draw(12, [&]{a.draw()});

参照によってキャプチャするオブジェクトを呼び出すラムダを作成する場所


1
これがCPPとタグ付けされているとすれば、これが最もクリーンなソリューションだと思います。すべての呼び出しでコピーする代わりに、std::functionin への定数参照を使用できますdraw
Sohaib

2
この例では関数がパラメーターをとらないため、プレースホルダーを使用しないでください。
kroiz

1

私はメンバー関数を静的ですべての作品として作成しました:

#include <iostream>

class aClass
{
public:
    static void aTest(int a, int b)
    {
        printf("%d + %d = %d\n", a, b, a + b);
    }
};

void function1(int a,int b,void function(int, int))
{
    function(a, b);
}

void test(int a,int b)
{
    printf("%d - %d = %d\n", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a;

    function1(10,12,test);
    function1(10,12,a.aTest); // <-- How should I point to a's aClass::test function?

    getchar();return 0;
}

「aのaClass :: testをfunction1の引数として使用するにはどうすればよいですか?」という主な質問を解決するだけではありません。ただし、クラスの外部コードを使用してクラスのプライベート変数を変更しないことをお勧めします
mathengineer

1

この信じられないほどシンプルなソリューションがなぜ受け継がれたのかわからない:

#include <stdio.h>

class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d\n", a, b, a + b);
    }
};

template<class C>
void function1(void (C::*function)(int, int), C& c)
{
    (c.*function)(1, 1);
}
void function1(void (*function)(int, int)) {
  function(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d\n", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a;

    function1(&test);
    function1<aClass>(&aClass::aTest, a);
    return 0;
}

出力:

1 - 1 = 0
1 + 1 = 2

0

実際にインスタンスを使用する必要がない場合a (つまり、@ mathengineerの回答のようにインスタンスを静的にすることができます)、非キャプチャラムダを渡すだけです。(関数ポインタに減衰)


#include <iostream>

class aClass
{
public:
   void aTest(int a, int b)
   {
      printf("%d + %d = %d", a, b, a + b);
   }
};

void function1(void (*function)(int, int))
{
    function(1, 1);
}

int main()
{
   //note: you don't need the `+`
   function1(+[](int a,int b){return aClass{}.aTest(a,b);}); 
}

ワンドボックス


注:aClass構築にコストがかかるか、副作用がある場合、これは良い方法ではない可能性があります。


-1

これで頭を叩くのをやめることができます。次に、単純なC関数を引数として受け取る既存の関数をサポートするメンバー関数のラッパーを示します。ディレクティブはここでの鍵です。thread_local

http://cpp.sh/9jhk3

// Example program
#include <iostream>
#include <string>

using namespace std;

typedef int FooCooker_ (int);

// Existing function
extern "C" void cook_10_foo (FooCooker_ FooCooker) {
    cout << "Cooking 10 Foo ..." << endl;
    cout << "FooCooker:" << endl;
    FooCooker (10);
}

struct Bar_ {
    Bar_ (int Foo = 0) : Foo (Foo) {};
    int cook (int Foo) {
        cout << "This Bar got " << this->Foo << endl;
        if (this->Foo >= Foo) {
            this->Foo -= Foo;
            cout << Foo << " cooked" << endl;
            return Foo;
        } else {
            cout << "Can't cook " <<  Foo << endl;
            return 0;
        }
    }
    int Foo = 0;
};

// Each Bar_ object and a member function need to define
// their own wrapper with a global thread_local object ptr
// to be called as a plain C function.
thread_local static Bar_* BarPtr = NULL;
static int cook_in_Bar (int Foo) {
    return BarPtr->cook (Foo);
}

thread_local static Bar_* Bar2Ptr = NULL;
static int cook_in_Bar2 (int Foo) {
    return Bar2Ptr->cook (Foo);
}

int main () {
  BarPtr = new Bar_ (20);
  cook_10_foo (cook_in_Bar);

  Bar2Ptr = new Bar_ (40);
  cook_10_foo (cook_in_Bar2);

  delete BarPtr;
  delete Bar2Ptr;
  return 0;
}

このアプローチの問題についてコメントしてください。

他の回答は既存のプレーンC関数を呼び出すことができません:http : //cpp.sh/8exun


3
したがって、std :: bindまたはラムダを使用する代わりに、グローバル変数に依存するインスタンスをラップします。他の回答と比較して、このアプローチの利点はわかりません。
スーパー

@super、他の回答では、プレーンC関数を引数として取り込んで既存の関数を呼び出すことはできません。
Necktwi、2018

2
問題は、メンバー関数を呼び出す方法です。無料の関数を渡すことは、問題のOPですでに機能しています。また、何も渡していません。ここにはハードコードされた関数とグローバルなFoo_ポインタしかありません。別のメンバー関数を呼び出す場合、これはどのようにスケールしますか?基本となる関数を書き直すか、ターゲットごとに異なる関数を使用する必要があります。
スーパー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.