C ++デリゲートとは何ですか?


147

C ++でのデリゲートの一般的な考え方は何ですか?それらは何ですか、どのように使用され、何に使用されますか?

最初に「ブラックボックス」の方法でそれらについて学びたいのですが、これらのことの根本についての少しの情報も素晴らしいでしょう。

これは最も純粋またはクリーンなC ++ではありませんが、私が作業しているコードベースには豊富に含まれています。それらを十分に理解したいと思っているので、それらを使用するだけで、ネストされた恐ろしいひどいテンプレートを掘り下げる必要はありません。

これらの2つのコードプロジェクトの記事は、私が何を意味するかを説明していますが、特に簡潔には説明していません。


4
.NETでのマネージC ++について話していますか?
dasblinkenlight 2012年


5
delegateC ++用語では一般的な名前ではありません。質問に情報を追加して、読んだコンテキストを含める必要があります。パターンは一般的かもしれませんが、一般的にデリゲートについて話す場合、またはC ++ CLIやdelegateの特定の実装を持つその他のライブラリのコンテキストでは、答えが異なる場合があることに注意してください。
デビッドロドリゲス-12

6
2年待つ?;)
rank1 2013年

6
まだ待っています...
Grimm The Opiner 2014年

回答:


173

C ++でデリゲートを実現するには、信じられないほど多くの選択肢があります。これが私の頭に浮かんだものです。


オプション1:ファンクタ:

関数オブジェクトは、 operator()

struct Functor
{
     // Normal class/struct members

     int operator()(double d) // Arbitrary return types and parameter list
     {
          return (int) d + 1;
     }
};

// Use:
Functor f;
int i = f(3.14);

オプション2:ラムダ式(C ++ 11のみ)

// Syntax is roughly: [capture](parameter list) -> return type {block}
// Some shortcuts exist
auto func = [](int i) -> double { return 2*i/1.15; };
double d = func(1);

オプション3:関数ポインター

int f(double d) { ... }
typedef int (*MyFuncT) (double d);
MyFuncT fp = &f;
int a = fp(3.14);

オプション4:メンバー関数へのポインター(最速のソリューション)

参照してください高速C ++デリゲート(上のコードプロジェクトを)。

struct DelegateList
{
     int f1(double d) { }
     int f2(double d) { }
};

typedef int (DelegateList::* DelegateType)(double d);

DelegateType d = &DelegateList::f1;
DelegateList list;
int a = (list.*d)(3.14);

オプション5:std :: function

(またはboost::function、標準ライブラリがサポートしていない場合)。遅いですが、最も柔軟性があります。

#include <functional>
std::function<int(double)> f = [can be set to about anything in this answer]
// Usually more useful as a parameter to another functions

オプション6:バインディング(std :: bindを使用)

いくつかのパラメータを事前に設定できるため、たとえばメンバー関数を呼び出すのに便利です。

struct MyClass
{
    int DoStuff(double d); // actually a DoStuff(MyClass* this, double d)
};

std::function<int(double d)> f = std::bind(&MyClass::DoStuff, this, std::placeholders::_1);
// auto f = std::bind(...); in C++11

オプション7:テンプレート

引数リストと一致する限り、何でも受け入れます。

template <class FunctionT>
int DoSomething(FunctionT func)
{
    return func(3.14);
}

2
すばらしいリスト、+ 1。ただし、ここでは実際にデリゲートとしてカウントされるのは2つだけです。ラムダとから返されたオブジェクトをキャプチャします。std::bind両方が実際に同じことを行います。ただし、ラムダは、異なる引数型を受け入れることができるという意味で多態性ではありません。
Xeo

1
@JN:関数ポインターを使用しないことをお勧めしますが、メンバーメソッドポインターの使用には問題がないように思われるのはなぜですか?それらはまったく同じです!
Matthieu M.

1
@MatthieuM。:公正なポイント。私は関数ポインタをレガシーであると考えていましたが、それはおそらく私の個人的な好みにすぎません。
JN

1
@Xeo:デリゲートの私の考えはかなり経験的です。多分私は多すぎる関数オブジェクトとデリゲートを混合しています(以前のC#の経験を非難します)。
JN、2012年

2
@SirYakalot関数のように動作するが、同時に状態を保持し、他の変数のように操作できるもの。たとえば、1つの用途は、2つのパラメーターを受け取る関数を取り、最初のパラメーターに特定の値を強制して、(bindingリスト内の)単一のパラメーターを持つ新しい関数を作成することです。関数ポインタではこれを実現することはできません。
JN 2012年

37

デリゲートは、オブジェクトインスタンスへのポインターまたは参照をラップするクラスであり、そのオブジェクトインスタンスで呼び出されるオブジェクトのクラスのメンバーメソッドであり、その呼び出しをトリガーするメソッドを提供します。

次に例を示します。

template <class T>
class CCallback
{
public:
    typedef void (T::*fn)( int anArg );

    CCallback(T& trg, fn op)
        : m_rTarget(trg)
        , m_Operation(op)
    {
    }

    void Execute( int in )
    {
        (m_rTarget.*m_Operation)( in );
    }

private:

    CCallback();
    CCallback( const CCallback& );

    T& m_rTarget;
    fn m_Operation;

};

class A
{
public:
    virtual void Fn( int i )
    {
    }
};


int main( int /*argc*/, char * /*argv*/ )
{
    A a;
    CCallback<A> cbk( a, &A::Fn );
    cbk.Execute( 3 );
}

呼び出しをトリガーする方法を提供するのはどれですか?デリゲート?どうやって?関数ポインタ?
サーヤカロット

デリゲートクラスはExecute()、デリゲートがラップするオブジェクトの関数呼び出しをトリガーするようなメソッドを提供します。
Grimm The Opiner 2012年

4
実行の代わりに、私はあなたのケースで呼び出し演算子をオーバーライドすることを強くお勧めします-void CCallback :: operator()(int)。この理由は、一般的なプログラミングにあり、呼び出し可能なオブジェクトは関数のように呼び出されることが期待されています。o.Execute(5)は互換性がありませんが、o(5)は呼び出し可能なテンプレート引数としてうまく適合します。このクラスは一般化することもできますが、簡潔にするために単純にしておくことをお勧めします(これは良いことです)。そのnitpick以外に、これは非常に便利なクラスです。
David Peterson

17

C ++デリゲート実装の必要性は、C ++コミュニティへの長期にわたる厄介な問題です。すべてのC ++プログラマーはそれらを用意したいので、次のような事実にもかかわらず、最終的にそれらを使用します。

  1. std::function() ヒープ操作を使用します(深刻な組み込みプログラミングには届きません)。

  2. 他のすべての実装は、移植性または標準への準拠を大なり小なり譲歩します(ここおよびcodeprojectでさまざまなデリゲート実装を検査して確認してください)。ワイルドなreinterpret_castsを使用しない実装、ネストされたクラスの「プロトタイプ」、ユーザーによって渡されたものと同じサイズの関数ポインターを生成できることをまだ見ていません。今回は別のクラスまたは同様の怪しげなテクニックを継承しています。これは、それを構築した実装者にとって素晴らしい成果ですが、C ++がどのように進化するかについての悲しい証言です。

  3. まれにしか指摘されていませんが、現在3つ以上のC ++標準リビジョンが存在するため、デリゲートは適切に対処されていません。(または、単純なデリゲート実装を可能にする言語機能の欠如。)

  4. C ++ 11ラムダ関数が標準で定義されている方法(各ラムダには匿名の異なる型があります)により、状況は一部のユースケースでのみ改善されました。ただし、(DLL)ライブラリAPIでデリゲートを使用するユースケースでは、ラムダだけを使用することはできません。ここでの一般的な手法は、最初にラムダをstd :: functionにパックし、それをAPI全体に渡すことです。


私は他の場所で見られる他のアイデアに基づいて、C ++ 11でElbert Maiのジェネリックコールバックのバージョンを実行しました。私にとって残っている唯一の悩ましい問題は、デリゲートを作成するためにクライアントコードでマクロを使用して立ち往生していることです。おそらくまだ解決していない、テンプレートの魔法の方法があるでしょう。
kert 2016

3
組み込みシステムでヒープなしのstd :: functionを使用していますが、それでも機能します。
prasad

1
std::function常に動的に割り当てられるわけではありません
オービットのライトネスレース

3
この答えは、実際の答えよりも怒りの言葉です
オービットのライトネスレース

7

非常に簡単に言えば、デリゲートは、関数ポインターがどのように機能するか(SHOULD)の機能を提供します。C ++の関数ポインタには多くの制限があります。デリゲートは、いくつかの舞台裏のテンプレートの厄介さを使用して、意図したとおりに機能するテンプレートクラスのfunction-pointer-type-thingを作成します。

つまり、特定の関数をポイントするように設定したり、いつでもどこでも好きな場所に渡したりして呼び出すことができます。

ここにいくつかの非常に良い例があります:


4

ここで特に言及されていないC ++でのデリゲートのオプションは、関数ptrとコンテキスト引数を使用してCスタイルで行うことです。これはおそらく、この質問をする多くの人が避けようとしているのと同じパターンです。しかし、パターンは移植可能で効率的であり、組み込みコードやカーネルコードで使用できます。

class SomeClass
{
    in someMember;
    int SomeFunc( int);

    static void EventFunc( void* this__, int a, int b, int c)
    {
        SomeClass* this_ = static_cast< SomeClass*>( this__);

        this_->SomeFunc( a );
        this_->someMember = b + c;
    }
};

void ScheduleEvent( void (*delegateFunc)( void*, int, int, int), void* delegateContext);

    ...
    SomeClass* someObject = new SomeObject();
    ...
    ScheduleEvent( SomeClass::EventFunc, someObject);
    ...

1

標準のC ++の関数オブジェクトに相当するWindowsランタイム。関数全体をパラメーターとして使用できます(実際には関数ポインターです)。主にイベントと組み合わせて使用​​されます。デリゲートは、イベントハンドラーが十分に満たすコントラクトを表します。関数ポインタがどのように機能するかを容易にします。

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