2016-11-08 9 views
3

私は、同期操作と非同期操作の両方を公開するC++ APIを設計しています。すべての操作が失敗し、失敗を報告する必要があります。非同期操作は、完了時に継続を実行する方法を提供する必要があります。私は最も読みやすく一貫した方法でAPIを設計しようとしています。同期操作と非同期操作を組み合わせたC++ API用の設計

これが今の私が持っているデザインを示す一例である:

#include <memory> 
#include <future> 

using namespace std; 

class Error { 
public: 
    Error(int c, string desc) : code(c), description(desc) {} 

    int code; 
    string description; 
}; 

template<typename T> 
class Callback { 
public: 
    virtual void completed(const T& result, unique_ptr<Error> error) = 0; 
}; 

template<typename T> 
class PrintCallback : public Callback<T> { 
public: 
    void completed(const T& result, unique_ptr<Error> error) override { 
     if (nullptr != error) { 
      printf("An error has occured. Code: %d Description: %s\n", 
        error->code, error->description.c_str()); 
     } else { 
      printf("Operation completed successfully. Result: %s\n", 
        to_string(result).c_str()); 
     } 
    } 
}; 

class API { 
public: 
    void asyncOperation(shared_ptr<Callback<int>> callback) { 
     thread([callback]() { 
      callback->completed(5, nullptr); 
     }).detach(); 
    } 

    int syncOperation(unique_ptr<Error>& error) { 
     return 5; 
    } 

    void asyncFailedOperation(shared_ptr<Callback<int>> callback) { 
     thread([callback]() { 
      callback->completed(-1, unique_ptr<Error>(new Error(222, "Async Error"))); 
     }).detach(); 
    } 

    int syncFailedOperation(unique_ptr<Error>& error) { 
     error = unique_ptr<Error>(new Error(111, "Sync Error")); 
     return -1; 
    } 
}; 

私は同期操作用のエラー出力パラメータの使用の使用と同期および非同期の署名の間に矛盾が好きではありません。彼らは非同期であり、彼らは彼らによって結果/失敗を返すためにコールバックを受け入れてみましょうかのように

  1. トリート同期操作: は、私は2つの選択肢を議論しています。このアプローチは、同期操作と非同期操作の間でより一貫性があり、少し洗練されています。一方、シンプルな同期操作をCallbackと連携させるのはちょっと変わった感じです。
  2. std::promisestd::futureを使用し、例外を使用して障害を報告してください。非同期操作では、std::futureが返され、get()が失敗した場合にスローされます。同期操作では、障害が発生した場合にスローされます。このアプローチは、エラー処理がメソッドシグニチャをクラッタさせず、例外がC++でのエラー処理を行う標準的な方法であるため、はるかにきれいに感じます。しかし、結果を得るにはfuture::get()に電話しなければならないので、ブロックしたくなければ、別のスレッドを起動して結果を待たなければなりません。また、非同期オペレーションの継続が実際にstd::promiseの結果を設定するスレッド上で実行されることも重要です。このアプローチは、この質問に受け入れられた答え - Synchronous and ASynchronous APIsです。

私は思ったんだけど:代替#2内の余分なスレッドを回避できる場合

  1. 代替案#2の反対がその賛否(特に余分なスレッド)よりも重要な場合。
  2. 私が考えていない別のアプローチがある場合。
  3. どのアプローチが可読性と一貫性のための最良の選択肢と考えられるでしょうか。

ありがとう!

+0

これは、Stack Overflowの問題が広すぎるためです。あなたは、APIデザインに関する書籍全体を書くことができます! :)しかし、私はニットを1つ持っています:* "例外はC++でエラー処理を行う標準的な方法です" * - いいえ、そうではありません。コンテキストに応じて、エラーコードやアサーションはしばしばよりクリーンです。 –

+0

[codereview.se]サイトは、既存の作業コードの改善に関する質問に適しています。 [ヘルプセンター](// codereview.stackexchange.com/help/on-topic)に相談した上で、そこに移行するためにあなたの質問にフラグを立てることをお勧めします。 –

+0

@ChristianHackl私はこれがかなり設計上の問題であると感じています。エラー処理に関する非同期メソッドと同期メソッドの間に一貫性の問題を提示しようとしました。ここでは、それを解決する方法(さまざまな選択肢)とそれらに対する私の懸念がありました。 – galsh83

答えて

1
  • Q:代替#2の余分なスレッドが回避できる場合。
  • A:余分なスレッドを回避する1つの方法は、タスクキューと共にスレッドプールを使用することです。この方法ではまだ余分なスレッドが使用されていますが、スレッドの数はこれまで作成されたタスクの数に比例するのではなく、固定システムのようなものです。

  • Q:代替案#2の反対がその賛否(特に余分なスレッド)を上回る場合。

  • A:そうは思わない。

  • Q:考えていない別のアプローチがある場合。

  • A:はい。私の意見では、すべての非同期化をboost :: asioと同様のAPIを提供し、boost :: asio :: io_serviceとlambda関数を活用し、スレッドプールとタスクキューを実装することが最善のアプローチです。次のように、syncをasyncのラッパーとして実装することができます。非同期呼び出しを行い、std :: condition_variableを待つ。あなたの非同期呼び出しのコールバックは、そのcondition_variableを通知します。

  • Q:どのアプローチが可読性と一貫性のために最良の選択肢と考えられるでしょうか。

  • A:非同期コードは、同期コードほど読みにくくはありません。読みやすさが本当にあなたにとって重要なのであれば、非同期をあきらめてください。一方、非同期と一貫性が必要な場合は、上で概説したようにすべてを非同期にします。

それに加えて、私の意見では、独自のErrorクラスを実装すべきではありません。 std :: error_code(とerror_condition、error_category)を見てください。素敵な記事はhttp://blog.think-async.comです。誰かがすでにあなたのためにそれを理解しました。

+0

ありがとう! 'std :: error_code'は私のErrorクラスの代わりに行く方法のように見えます。リンクしたブログ投稿は本当に役に立ちます。私はboostの 'io_service'について読んで始めましたが、一般的に、それぞれの操作を独自のクラスにするのは面白いアプローチです。今私は 'std :: future'を使うことに傾いています。 – galsh83

関連する問題