2016-11-08 125 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. 使用std::promisestd::future,並使用異常來報告故障。對於異步操作,將返回std::future,並在發生故障時拋出它的get()。同步操作只會在發生故障時拋出。這種方法感覺更清晰,因爲錯誤處理不會喧譁方法簽名,而異常是在C++中執行錯誤處理的標準方式。然而,爲了得到結果,我必須調用future::get(),所以如果我不想阻塞,那麼我必須啓動另一個線程來等待結果。異步操作的繼續在實際上在std::promise上設置結果的線程上運行也很重要。這種方法是這個問題的公認答案 - Synchronous and ASynchronous APIs

我想知道:

  1. 如果能夠避免替代#2額外的線程。
  2. 如果替代#2的缺點超過它的優點(特別是額外的線程)。
  3. 如果還有另一種方法,我沒有考慮。
  4. 哪種方法將被認爲是可讀性和一致性的最佳選擇。

謝謝!

+0

就目前而言,Stack Overflow的問題太廣泛了。你可以寫一本關於API設計的書! :)但是,我確實有一個挑剔:*「異常是在C++中執行錯誤處理的標準方法」* - 不,它們不是。取決於上下文,錯誤代碼或斷言通常更清晰。 –

+0

[codereview.se]網站更適合於改善現有工作代碼的問題。我建議你在諮詢他們的[幫助中心](// codereview.stackexchange.com/help/on-topic)後,在那裏標記你的問題。 –

+0

@ChristianHackl我覺得這應該是一個非常集中的設計問題。我試圖在異步和同步方法之間提出一個關於錯誤處理的一致性問題,在那裏我研究瞭如何解決它(不同的選擇)以及我對它們的擔憂。 – galsh83

回答

1
  • 問:如果可以避免替代方案#2中的額外線程。
  • 答:避免多餘線程的一種方法是將線程池與任務隊列一起使用。該方法仍然使用額外的線程,但線程的數量是某種固定的系統方式,而不是與創建的任務數量成比例。問:如果替代方案#2的利弊超過它的利弊(尤其是額外的線程)。 A:我不這麼認爲。

  • 問:如果還有另一種方法我沒有考慮。 A:是的。在我看來,最好的方法是使一切異步,提供類似的API來boost :: asio,利用boost :: asio :: io_service和lambda函數,並實現一個線程池和任務隊列。您可以將異步實現爲異步封裝,如下所示:進行異步調用並等待std :: condition_variable。異步調用信號的回調函數condition_variable。

  • 問:哪種方法會被認爲是可讀性和一致性的最佳選擇。

  • 答:異步代碼不會像同步代碼那樣可讀。如果可讀性對您來說確實非常重要,請放棄異步。另一方面,如果您想要異步和一致,請按照上面概述的那樣進行一切異步操作。

除此之外,我認爲你不應該實現你自己的錯誤類。看看std :: error_code(還有error_condition,error_category)。一篇不錯的文章是http://blog.think-async.com。有人已經爲你弄明白了。

+0

謝謝! 'std :: error_code'看起來像要走的路,而不是我的Error類,並且你鏈接的博客文章真的很有幫助。我開始閱讀關於boost的'io_service',但總的來說,讓每個操作成爲它自己的類是一個有趣的方法來考慮。現在我傾向於使用'std :: future'。 – galsh83