抛出异常与 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
,或者当项目可用时 true
。 pop()
函数的其余部分可以保持不变。
不要阻止。相反 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++ 异常。使用变体类型并不意味着任何额外的动态分配——这样的代码可以是高效的并且不会增加太多复杂性。
我正在实施自己的队列,该队列在 .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
的函数,如果超时则轮询和 returnsfalse
,或者当项目可用时true
。pop()
函数的其余部分可以保持不变。不要阻止。相反 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++ 异常。使用变体类型并不意味着任何额外的动态分配——这样的代码可以是高效的并且不会增加太多复杂性。