I see some things that may help you improve your code.
List all required #include
s
The code needs the following #include
s to actually compile. Since they therefore form part of the interface, they should be included in the file and in a code review:
#include <mutex>
#include <queue>
Be clear about ownership
If the intention is to have a thread-safe queue, then passing pointers in and out is definitely not the way to go. The problem is with object ownership. Even if your thread-safe queue works perfectly, all of its inherent goodness is all too easily bypassed because you're using pointers. For example:
SafeQueue<std::string> sq;
{
std::string msg1{"this message exists"};
sq.push(&msg1);
} // msg1 is now destroyed, but queue still has pointer
std::cout << *sq.next() << " no longer\n"; // kaboom!
The problem is that the queue doesn't actually own the object (or at least have a std::shared_ptr
) so there's not much point in perfecting the queue until that's addressed.
Choose better names
I have never thought of push
and next
as inverse operations, and I'll bet you never have either. The push
member function name is OK, since it actually does that, but next
is just a strange name. I'd say use pop
or pop_front
might be better names.
Minimize locking duration
In the push
code, we have this:
void push(T* elem) {
m.lock();
if(elem != nullptr) {
q.push(elem);
}
m.unlock();
}
But why acquire a lock if you don't need it? It just slows things down. You could instead write that like this:
void push(T* elem) {
if (elem == nullptr) {
return;
}
m.lock();
q.push(elem);
m.unlock();
}
Or better, see the next suggestion:
Use RAII to reduce errors
If you happened to forget to remove the lock on the code above, Bad Things would likely happen. Fortunately, in C++, there's a handy idiom that is very often used in C++ and it's called Resource Allocation is Initialization. In this context, we use a std::lock_guard
like this:
void push(T elem) {
std::lock_guard<std::mutex> lock(m);
q.push(elem);
}
The lock_guard
automatically gets locked on creation and unlocked on deletion, so when it goes out of scope, the lock is released even if you forget.
Return an indicator of success
Since we've already established that passing pointers in or out is a problem, I'd suggest changing the interface for the next()
function. Have it take a reference (so the caller must supply one) and then return a bool
to indicate success. That might look like this:
bool next(T& elem) {
std::lock_guard<std::mutex> lock(m);
if (q.empty()) {
return false;
}
elem = q.front();
q.pop();
return true;
}