抛出异常vs返回代码

时间:2016-06-13 12:14:06

标签: c++ exception return-value blocking

我正在实施我自己的队列,该队列在.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是我的自定义异常。现在,从性能的角度来看,我一直在考虑这个异常。从该函数返回某种返回代码会更好吗?这对性能有何影响?

此外,由于.pop已经返回了一些内容,您将如何实现其他返回代码?我想一些包含T和返回代码的新结构将是必需的。复杂性的增加真的值得吗?

4 个答案:

答案 0 :(得分:4)

在未达到预期时抛出异常,在查询状态时返回状态代码。

例如:

/// 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风格的反应堆路线与基于执行人的期货相结合:

/// 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
  }
});

答案 1 :(得分:1)

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

他暂时给了nice talk一段时间。我们的想法是expected<T>包含T,或者它包含一个异常/错误代码对象,用于描述无法生成T的原因。

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

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

这实际上非常接近于如何在Rust中以错误方式完成错误处理。 C.F. 2 3

答案 2 :(得分:0)

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

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

  • 阻止直到某个项目可用。创建一个名为wait的函数,如果超时则轮询并返回false,或者当项目可用时返回true。您pop()功能的其余部分可以保持不变。

  • 不要阻止。而是返回状态:

    • 如果操作会阻止,请返回“busy”
    • 如果队列为空,请返回“empty”
    • 否则,您可以“弹出”并返回“成功”

由于您有互斥锁,因此这些选项似乎优于非等待功能。

答案 3 :(得分:0)

  

此外,由于.pop已经返回了一些内容,您将如何实现其他内容   返回代码?我想一些既包含T又包含返回码的新结构   需要。

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

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