I'm implementing my own queue which blocks on .pop()
. This function also accepts additional argument which is a timeout. So at the moment I have such code:
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;
};
}
where exceptions::Timeout
is my custom exception. Now I've been thinking about this exception throwing from the performance point of view. Would it be better to return some kind of return code from that function? How does that affect performance?
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. Is that increase in complexity really worth it?
Throw exceptions when an expectation has not been met, return a status code when you're querying for status.
for example:
/// 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;
and then there's the asio-style reactor route coupled with executor-based futures:
/// 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);
which could be called like this:
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
}
});