C++ソフトC#シリーズ-std::function

8516 ワード

C#を使ったことがある人は、依頼や事件をよく知っています.
C#を使ったことがなければ、ここで簡単に依頼を説明しますが、使ったことがあれば、次はskipでいいです.
依頼はメソッド宣言であり,C言語では関数ポインタでアドレスを表すCALLメソッドが可能であり,依頼はC#でも少なくないことが分かっている.
しかし、依頼にはいくつかの違いがあります.主なところは、C++の中で、
関数ポインタは「オブジェクト向け」ではありません.たとえば、クラスCTestがあり、クラスにメンバーメソッドfooがあります.この場合、関数ポインタでfooを呼び出すには、fooがクラスCTestのメンバーであるため、CTestのインスタンスthisポインタが必要です.
たとえば、
class CTest
{
    public:
        void foo(int i) {}
};
メンバー関数ポインタは、次のように書く必要があります.
void (CTest::*FuncFoo)(int);
このメンバーがconst修飾である場合、次のようになります.
void (CTest::*FuncFoo)(int) const;

呼び出しはどうしますか?次のようにします.
CTest* pThis = new...;
FuncFoo pThis_foo = &CTest::foo;
(pThis->*pThis_foo)(123);
そう書けば
pThis->*pThis_foo(123);
は、->*という演算子の優先度が低いため、問題が発生します...
もしCTestがHEAPになかったら、stackにあったのでしょうか?
次のようにします.
CTest pThis;
(pThis.*pThis_foo)(123);

これらの奇抜な文法は,->*,.*この不思議な演算子は本当に卵が痛い.
スレッドを起動するとき、static関数からこのようなsbの構文を通じて、クラスthisをパラメータとして渡し、クラスのメンバー関数を走るのが一般的です.
でもC#では、
委任はオブジェクト向けで、クラスインスタンスのメンバー関数ポインタを委任で表すことができます.
例:
namespace ConsoleApplication1
{
    public delegate void CallbackFunc(int i);
    //void (callback*)(int i);

    class CNewTest
    {
        private static readonly string _str = "this CNewTest.";

        public void MemberCallback(int i)
        {
            System.Diagnostics.Debug.WriteLine(i.ToString());
            System.Diagnostics.Debug.WriteLine(_str);
        }
    }

    class Program
    {
        static void StaticCallback(int i)
        {
            System.Diagnostics.Debug.WriteLine(i.ToString());
        }

        static void Main(string[] args)
        {
            CallbackFunc StaticCbF = new CallbackFunc(StaticCallback);
            StaticCbF(123); //Call to static void StaticCallback(int i)

            CNewTest c = new CNewTest();
            CallbackFunc MembCbF = new CallbackFunc(c.MemberCallback);
            MembCbF(1234); //Call to c->MemberCallback

            Console.ReadKey();
        }
    }
}
出力:
123
1234
this CNewTest.
つまり、C#では、メソッドが依頼声明に適応すれば、callを呼び出すことができ、C#は自動的にthisポインタを隠してくれます.
したがって,C#にイベントが誕生し,1つのプロバイダオブジェクトが1つの依頼を定義し,依頼をイベントとして宣言すると,他のリスナーオブジェクトがこのイベントを自分のメンバー関数に関連付けることができ,プロバイダが必要に応じてこの依頼を呼び出すのを待つと,メンバー関数が呼び出され,イベントがトリガーされる.
なぜ事件と宣言し、依頼を直接暴露すればいいのではないでしょうか.
これは論理的な問題で、直接依頼を暴露すれば、購読者は自発的に依頼をトリガーすることができ、購読者は自発的に、また「購読」することができます.
イベントを使用する場合、サブスクライバはプロバイダが依頼をトリガーするのを待つしかなく、この場合、サブスクライバは「受動的」になるしかありません.
この概念は、C++で理解されています.つまり、
クラスAにはpublicのptr、ptr_がありますthis、このptrはメンバー関数ポインタを保存します.ptr_thisはptrのクラスインスタンスを保存します.
類Bはこのptrを自分のptrに変えた.ptr_thisをthisに変更し、このメンバー関数ポインタの宣言に適応します.
クラスAはいくつかの仕事が終わった後、ptrをチェックします!=nullptr,callというptrはクラスBのメンバ関数に呼び出される.
しかしC++はこれらの天然の不足があって、ptrはnullptrではありませんてむしろできて、しかしクラスBはとっくにdeleteされたかもしれなくて、しかしあなたのクラスAのptrに保存して依然としてnullptrではありませんて、あなたのcallは直接掛けて、もちろんC 11のshared_を使いますptr、weak_ptrというものは、解決策でもありますが、総じて言えば、不快で、これはどんなに面倒ですか.
つまり、C++では、メモリを手動で管理する輪から抜け出すことはできません.
C#のGC自動管理はこれらの問題を完全にクリアします.
もう1つは、C#の中で、依頼はマルチキャストの実現です.例えば:
        static void Main(string[] args)
        {
            //CallbackFunc StaticCbF = new CallbackFunc(StaticCallback);
            //StaticCbF(123); //Call to static void StaticCallback(int i)

            //CNewTest c = new CNewTest();
            //CallbackFunc MembCbF = new CallbackFunc(c.MemberCallback);
            //MembCbF(1234); //Call to c->MemberCallback

            CallbackFunc cbF = new CallbackFunc(StaticCallback);
            CNewTest c = new CNewTest();
            cbF += c.MemberCallback;
            cbF(123);

            Console.ReadKey();
        }
出力:
123
123
this CNewTest.
简単に言えば、cbFというものの中に、汎用的なリストがあります...Callの時は、ランニングメーターが1つずつcallが来ていて、とても便利でした.
はっきり言って、依頼は1つの【方法のインタフェース】で、方法が正しければいいので、安全で気持ちがいいです.
そこで依頼+イベントというマルチキャストを実現することで,観察者モードをうまく作ることができる.
そんなに多くの弊害が出てきて、C 11委員会もこれらの問題を発見して、std::function、これは、簡単に言えば、それは依頼の機能を実現するのにあまり役に立たない(事件を実現するのではありません).
やはり上のC#に書いてあるConsoleApplication 1に戻って、std::functionでもう一度やり直します.
(コンパイル環境はVS 2013、Windows SDK.)
std::functionを使用する前に、includeを含める必要があります.
関数を用意します.
class CNewTest
{
    char m_str[128];

public:
    void MemberCallback(int i)
    {
        strcpy(m_str,"this CNewTest.
"); printf("%d
",i); printf(m_str); } }; void StaticCallback(int i) { printf("%d
",i); }

直接的な関数、クラスメンバー関数です.
まず、StaticCallbackを呼び出します.
int main()
{
    std::function<void (int)> fp = std::function<void (int)>(&StaticCallback);
    fp(123);

    getchar();
    return 0;
}
出力:123
このように書くのは少し多いですね.うん、私たちはC++で、typedefがあります.
int main()
{
    typedef std::function<void (int)> declare_fp;

    declare_fp fp = declare_fp(&StaticCallback);
    fp(123);

    getchar();
    return 0;
}

typedefは1行のコードが多くて、しかも直観的ではありませんて、恐れないで、私达はautoがあります.
int main()
{
    auto fp = std::function<void (int)>(&StaticCallback);
    fp(123);

    getchar();
    return 0;
}
実際の使用では、状況に応じてautoかtypedefかを決め、両者を組み合わせたほうがよい.
(
std::functionはlamdbaという匿名関数と組み合わせて使用することもできます.逆天レベルの効果があります.ここではあまり詳しくありません.私は後でlamdbaの文章を書くかもしれません.)
このtmdの依頼がC#に似ているような気がしますか?しかし、C#の依頼はnewで、私たちのC++のは直接stackの上で開くことができて、もちろん私たちのnew、同じくできます.
int main()
{
    typedef std::function<void (int)> declare_fp;

    declare_fp* fp = new declare_fp(&StaticCallback);
    (*fp)(123);

    delete fp; //    

    getchar();
    return 0;
}

次に、呼び出しメンバー関数ポインタをテストします.
int main()
{
    auto fp = std::function<void (int)>(&StaticCallback);
    fp(123);

    CNewTest* c = new CNewTest();
    auto fp2 = std::bind(&CNewTest::MemberCallback,c,std::placeholders::_1);
    fp2(1234);

    delete c;

    getchar();
    return 0;
}
出力:
123
1234
this CNewTest.
std::bindを使用してstd::functionを生成し、このfunctionを呼び出すと過去のメンバー関数を呼び出すことができます.
今私たちのCNewTestはHeapにいます.もし私たちがstackにいたら?きっと思いついたでしょう.
    CNewTest c;
    auto fp2 = std::bind(&CNewTest::MemberCallback,&c,std::placeholders::_1);
    fp2(1234);

多くの人がおかしいかもしれませんが、後ろのstd::placeholders::1どういう意味ですか.
実はbindの上のこの関数の方法を表すのは1つのパラメータがあって、このパラメータは汎型で、もし私たちがそれに汎型のパラメータを生成させないならば、直接1つの絶対値を書いて、例えば:
    CNewTest* c = new CNewTest();
    auto fp2 = std::bind(&CNewTest::MemberCallback,c,3333);
    fp2();
の出力は次のとおりです.
123
3333
this CNewTest.
このように呼び出しても:
    CNewTest* c = new CNewTest();
    auto fp2 = std::bind(&CNewTest::MemberCallback,c,3333);
    fp2(1234);
出力は依然として上であり、デフォルトパラメータを強制する役割と理解できる.
std::functionは実際の応用では、callbackパラメータを持つ関数があると仮定し、いくつかのタスクが完了すると、callというcallback関数があります.
void DoWork(std::function<void (int)> callback)
{
    //DoWork....
    int result[] = {1,2,3};
    for (int i : result)
        callback(i);
}

次のように呼び出すことができます.
int main()
{
    CNewTest c;
    auto fp2 = std::bind(&CNewTest::MemberCallback,&c,std::placeholders::_1);
    DoWork(fp2);

    getchar();
    return 0;
}

出力:
1
this CNewTest.
2
this CNewTest.
3
this CNewTest.
前述したようにstd::functionはnew可能です.つまり、このような関数を設計することができます.
void DoWork2(std::shared_ptr<std::function<void (int)>> callback)
{
    //DoWork2....
    int result[] = {1,2,3};
    for (int i : result)
        (*callback.get())(i);
}

次のように呼び出されます.
int main()
{
    {
        CNewTest c;
        auto fp = std::make_shared<std::function<void (int)>>
            (std::function<void (int)>(std::bind(&CNewTest::MemberCallback,&c,std::placeholders::_1)));
        DoWork2(fp);

        //delete fp.
    }

    getchar();
    return 0;
}

shared_ptrは私たちのnewのstd::functionが安全なdeleteであることを保証します.
この針はあちこちに投げてもいいです...C#の依頼より自由度が大きい.
std::function自体はマルチキャストの実現がなく、std::vectorなどを自分で包んで自分で作ることができます.