C ++でコールバックを実装するためのいくつかのエレガントな方法

目次

1つは、関数ポインタを使用する

次に、仮想メソッドを実装します

3つ目は、std :: futureとstd :: asyncを使用する


 

2つのクラス間の非同期シナリオを考えてみましょう。クライアントは特定のタスクを処理するためにクラスAを呼び出し、AはBに処理を実行して結果を返すように委託する必要があります。すべてが非同期で実行されます。つまり、関数はすぐに返され、その後計算はバックグラウンドで実行され、通知が完了します。発信者。

1つは、関数ポインタを使用する

クラスBには、関数ポインターを受け取るためのSetCallBackインターフェイスが必要です。ここでは、std :: functionを使用して実現します。B. ProcessAsyncタスクが実行されると、別のスレッドが開始され、デタッチされて、より長いバックグラウンド計算が実行されます(ここでは、std :: this_thread :: sleep_forを使用して、長い計算をシミュレートします)。計算が完了すると、次の関数ポインターが設定されたばかりは、発信者に通知するために使用されます。

#include <iostream>
#include <functional>
#include <thread>

class B
{
public:
    void SetCallBack(const std::function<void(double)> &cb) {
        m_callback = cb;
    }
    void ProcessAsync() {
        printf("class B, create thread to compute and return immediately\n");
        std::thread th(&B::ProcessReal, this);
        th.detach();
    }
    
private:
    void ProcessReal() {
        printf("class B, Process for a long time...\n");
        std::this_thread::sleep_for(std::chrono::seconds(3));
        if (m_callback) {
            printf("callback\n");
            m_callback(3.72);
        }
    }
    std::function<void(double)> m_callback;
};

クラスAは、コールバックをクラスBに設定するときに、ラムダ式またはstd :: bindのいずれかを使用できます。これは、個人の好みによって異なります。Aがコールバックを受信したら、計算結果を出力します。

#include <iostream>
#include <memory>
#include <functional>
#include "B.h"

class A
{
public:
    void DoTask() {
        printf("class A, DoTask\n");
        m_b = std::make_shared<B>();
        //m_b->SetCallBack([this](double res) {return OnProcessDone(res);});
        m_b->SetCallBack(std::bind(&A::OnProcessDone, this, std::placeholders::_1));
        m_b->ProcessAsync();
    }
private:
    void OnProcessDone(double result) {
        printf("OnProcessDone, result = %f\n", result);
    }
    std::shared_ptr<B> m_b{nullptr};
};

主な機能については言うまでもありません

#include <thread>
#include <chrono>
#include "A.h"

int main() {
    A a;
    a.DoTask();
    std::this_thread::sleep_for(std::chrono::seconds(5));
    return 0;
}

 

次に、仮想メソッドを実装します

Bは、純粋仮想メソッドOnProcessDoneを持つインターフェイスクラスListenerを提供し、AはB :: Listenerを継承してこの仮想メソッドを実装するために、新しいMyListenerを宣言できます。

ここでは、A :: MyListenerとB :: Listenerの両方が内部クラスとして宣言されています。これの利点は、内部クラスがデフォルトで外部クラスのフレンドであるため、A :: MyListenerがクラスAのプライベートメンバーにアクセスできることです。 、コールバック内この書き込み方法は、プライベートメンバーにアクセスする必要がある場合に非常に便利です。

#include <iostream>
#include <functional>
#include <thread>
#include <memory>

class B
{
public:
    class Listener {
    public:
        virtual void OnProcessDone(double result) = 0;
    };
    void SetCallBack(const std::shared_ptr<Listener> &cb) {
        m_callback = cb;
    }
    void ProcessAsync() {
        printf("class B, create thread to compute and return immediately\n");
        std::thread th(&B::ProcessReal, this);
        th.detach();
    }
    
private:
    void ProcessReal() {
        printf("class B, Process for a long time...\n");
        std::this_thread::sleep_for(std::chrono::seconds(3));
        if (m_callback) {
            printf("callback\n");
            m_callback->OnProcessDone(3.72);
        }
    }
    std::shared_ptr<Listener> m_callback;
};
#include <iostream>
#include <memory>
#include <functional>
#include "B.h"

class A
{
public:
    void DoTask() {
        printf("class A, DoTask\n");
        m_b = std::make_shared<B>();
        m_b->SetCallBack(std::make_shared<MyListener>(this));
        m_b->ProcessAsync();
    }
private:
    class MyListener : public B::Listener {
    public:
        MyListener(A *a) : m_a(a) {}
        void OnProcessDone(double result) {
            printf("OnProcessDone, result = %f\n", result);
            m_a->m_res = result;
        }
    private:
        A *m_a;
    };
    
    std::shared_ptr<B> m_b{nullptr};
    double m_res;
};

 

3つ目は、std :: futureとstd :: asyncを使用する

クラスAでstd :: asyncを開始して、Bの同期処理関数を非同期にします。これにより、Bにコールバックを設定する必要がなくなり、Bの実装が非常に簡単になります。Aでは、結果を計算する必要があるときにfutureの.get()を呼び出すことで、結果を取得できます。

#include <iostream>
#include <thread>

class B
{
public:
    double ProcessSync() {
        printf("class B, Process for a long time...\n");
        std::this_thread::sleep_for(std::chrono::seconds(3));
        return 3.72;
    }
};

 std :: futureはコピーできないため、futureを別のスレッドに渡すときに移動することに注意してください。

#include <iostream>
#include <memory>
#include <functional>
#include <future>
#include "B.h"

class A
{
public:
    void DoTask() {
        printf("class A, DoTask\n");
        m_b = std::make_shared<B>();
        std::future<double> fut = std::async(std::bind(&B::ProcessSync, m_b));
        std::thread th(&A::WaitForProcessDone, this, std::move(fut));
        th.detach();
    }
private:
    void WaitForProcessDone(std::future<double> &&fut) {
        double result = fut.get();
        printf("ProcessDone, result = %f\n", result);
        m_res = result;
    }
    std::shared_ptr<B> m_b{nullptr};
    double m_res;
};

 

おすすめ

転載: blog.csdn.net/yuejisuo1948/article/details/113280498