抛出异常与 return 代码

Throwing exception vs return code

我正在实施自己的队列,该队列在 .pop() 上阻塞。此函数还接受附加参数,即超时。所以目前我有这样的代码:

template <class T>
class BlockingQueue {

private:
    std::queue<T> m_queue;
    std::mutex m_mutex;
    std::condition_variable m_condition;

public:
    T pop(uint64_t t_millis) {
        std::unique_lock<std::mutex> lock(m_mutex);
        auto status = m_condition.wait_for(
            lock,
            std::chrono::milliseconds(t_millis),
            [=] {
                return !m_queue.empty();
            }
        );
        if (!status) {
            throw exceptions::Timeout();
        }
        T next(std::move(m_queue.front()));
        m_queue.pop();
        return next;
    };
}

其中 exceptions::Timeout 是我的自定义异常。现在我一直在从性能的角度考虑抛出这个异常。 return 来自该函数的某种 return 代码会更好吗?这对性能有何影响?

此外,由于 .pop 已经 return 了,您将如何实现额外的 return 代码?我想需要一些同时包含 T 和 return 代码的新结构。增加复杂性真的值得吗?

未满足期望时抛出异常,return查询状态时的状态代码。

例如:

/// pops an object from the stack
/// @returns an object of type T
/// @pre there is an object on the stack
/// @exception std::logic_error if precondition not met
T pop();

/// queries how many objects are on the stack
/// @returns a count of objects on the stack
std::size_t object_count() const;

/// Queries the thing for the last transport error
/// @returns the most recent error or an empty error_code
std::error_code last_error() const;

然后是 asio 风格的 reactor 路线加上基于执行器的期货:

/// Asynchronously wait for an event to be available on the stack.
/// The handler will be called exactly once.
/// to cancel the wait, call the cancel() method
/// @param handler is the handler to call either on error or when
///        an item is available
/// @note Handler has the call signature void(const error_code&, T)
///
template<class Handler>
auto async_pop(Handler handler);

可以这样称呼:

queue.async_pop(asio::use_future).then([](auto& f) {
  try {
    auto thing = f.get();
    // use the thing we just popped
  }
  catch(const system_error& e) {
    // e.code() indicates why the pop failed
  }
});

这里不需要例外。 "timeout" 与从队列中获取项目一样是预期的结果。 没有超时,程序本质上等同于停机问题。假设客户指定他们想要无限期超时。异常会抛出吗?你会如何处理这样的异常(假设你在这个 post-世界末日的场景中还活着?)

相反,我发现这两个设计选择更合乎逻辑(尽管它们不是唯一的选择):

  • 在项目可用之前阻止。创建一个名为 wait 的函数,如果超时则轮询和 returns false,或者当项目可用时 truepop() 函数的其余部分可以保持不变。

  • 不要阻止。相反 return 状态:

    • 如果操作会阻塞,return "busy"
    • 如果队列为空,return "empty"
    • 否则,您可以 "pop" 和 return "success"

因为你有一个互斥体,这些选项似乎比非等待函数更可取。

Also since .pop already returns something how would you implement additional return code? I suppose some new structure that holds both T and a return code would be needed.

采用这种方法会对可与您的 BlockingQueue 一起使用的类型提出额外要求:它们必须是默认可构造的。如果 pop() returns 通过 std::unique_ptr 结果(用 nullptr 发出超时信号)可以避免这种情况,但这会引入明显的开销。

我认为在这里使用异常没有任何缺点。如果您以毫秒为单位测量超时,那么在超时情况下处理异常应该可以忽略不计。

在这种情况下发出错误信号而不抛出异常的一种方法是使用类似于 Andrei Alexandrescu 的 expected<T> 模板。

他不久前就此事发表了 nice talk。这个想法是,expected<T> 要么包含一个 T,要么它包含一个异常/错误代码对象,描述为什么无法生成 T

您可以使用他的实现,或根据您自己的目的轻松调整想法。例如,您可以很容易地在 boost::variant<T, error_code> 之上构建这样的 class。

这只是另一种错误处理方式,不同于 C 风格的整数错误代码和 C++ 异常。使用变体类型并不意味着任何额外的动态分配——这样的代码可以是高效的并且不会增加太多复杂性。

这实际上与 Rust 惯用的错误处理方式非常接近。 c.f。 2 3