2016-11-08 129 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