As some practice, I decided to implement a non-complete threadsafe and generic stack. You can see an initial declaration of the class template below:
template <typename T>
class ThreadsafeStack {
public:
ThreadsafeStack() = default;
ThreadsafeStack(ThreadsafeStack<T> const&);
template <typename U>
ThreadsafeStack(ThreadsafeStack<U> const&);
~ThreadsafeStack() = default;
ThreadsafeStack<T>& operator=(ThreadsafeStack<T> const&);
template <typename U>
ThreadsafeStack<T>& operator=(ThreadsafeStack<U> const&);
void push(T new_value);
std::shared_ptr<T> pop();
void pop(T& value);
bool empty() const;
private:
std::stack<T> data;
mutable std::mutex m;
};
My problem is about the generalized copy operations (for simplicity, I left out the move operations for now). As an example, see the definition of the "normal" and generalized assignment operators:
// "Normal"
template <typename T>
ThreadsafeStack<T>& ThreadsafeStack<T>::operator=(ThreadsafeStack<T> const& o)
{
if ((void*) this == (void*) &o) {
return *this;
}
std::lock(m, o.m);
std::lock_guard<std::mutex> lock1(m, std::adopt_lock);
std::lock_guard<std::mutex> lock2(o.m, std::adopt_lock);
data = o.data;
return *this;
}
// "Generalized"
template <typename T>
template <typename U>
ThreadsafeStack<T>& ThreadsafeStack<T>::operator=(ThreadsafeStack<U> const& o)
{
if ((void*) this == (void*) &o) {
return *this;
}
// ... ?
return *this;
}
With this code, the problem is that the member std::mutex m
of ThreadsafeStack<U>
is not
visible from ThreadsafeStack<T>
- so I can lock m
but not o.m
(unlike in the "normal" case).
(Note that I have to lock the mutex of the data structure to be copied without breaks during the whole copying process. I don't think that locking and releasing by elements would be either efficient or logically correct. Consider that I have a stack consisting of values 5, 6, 7; first, I copy 5 but after that another thread pops 6 so the next element I copy is 7...)
So, how would you solve the problem above?
I already considered the following solutions:
- make the mutex member public (worst idea, don't want to do that). (Returning a reference to them with a member function is a similar bad OO design idea in my opinion.)
- Second one: create a public member function that returns a copy of the complete underlying data (note that this way locking must be performed only once). I think a longer, but similar workaround could be to use the already present interface.
- Doesn't exist there any language provided, more natural way to do this? (For example, some special syntax that denotes that all instantiations of a specific template see each other's private and protected members for such purposes.)
Also tried to do some research in the topic:
in C++ Templates - The Complete Guide the authors also pointed out in Chapter 5 that because of this problem they had to use the public interface to make the copy. Despite I find this a great book, unfortunately it's a little bit old so there might be more efficient solutions for this nowadays.
This might be also true (in connection with certain items) for Scott Meyer's Effective C++: Item 45 mentioned "normal" and generalized copy constructors/assignment operators, but it wasn't deep enough in this specific direction.
Read similar questions on SO but didn't find such a specific one in connection with generalized operations.
(Note: the basics of the threadsafe stack example came from Anthony Williams' C++ Concurrency in Action.)
Thank you very much for any help in advance.
Regards, Zsolt