Reputation: 685
Suppose I have a class:
class WonnaBeMovedClass
{
public:
WonnaBeMovedClass(WonnaBeMovedClass&& other);
void start();
private:
void updateSharedData();
std::vector<int> _sharedData;
std::thread _threadUpdate;
std::mutex _mutexShaderData;
//other stuff
};
WonnaBeMovedClass::WonnaBeMovedClass(WonnaBeMovedClass&& other)
{
_sharedData = std::move(other._sharedData);
_threadUpdate = std::move(other._threadUpdate);
_mutexShaderData = std::move(other._mutexShaderData); //won't compile.
}
void WonnaBeMovedClass::start()
{
_threadUpdate = std::thread(&updateSharedData, this);
}
void WonnaBeMovedClass::updateSharedData()
{
std::lock_guard<std::mutex> lockSharedData(_mutexShaderData);
for (auto& value : _sharedData)
{
++value;
}
}
It won't compile because mutex cannot be moved. It doesn't make sense. Then I thought that it is possible to workaround this by using pointers instead of actual variables and came up with the following:
class WonnaBeMovedClass
{
public:
WonnaBeMovedClass(WonnaBeMovedClass&& other);
void start();
private:
void updateSharedData();
std::vector<int> _sharedData;
std::unique_ptr<std::thread> _threadUpdate //pointer;
std::unique_ptr<std::mutex> _mutexShaderData //pointer;
//other stuff
};
WonnaBeMovedClass::WonnaBeMovedClass(WonnaBeMovedClass&& other)
{
_sharedData = std::move(other._sharedData);
_threadUpdate = std::move(other._threadUpdate);
_mutexShaderData = std::move(other._mutexShaderData); //won't compile.
}
void WonnaBeMovedClass::start()
{
_threadUpdate = std::make_unique<std::thread>(&updateSharedData, this);
}
void WonnaBeMovedClass::updateSharedData()
{
std::lock_guard<std::mutex> lockSharedData(*_mutexShaderData);
for (auto& value : _sharedData)
{
++value;
}
}
So now when I:
WonnaBeMovedClass object1;
WonnaBeMovedClass object2;
//do stuff
object1 = std::move(object2);
I actually move addresses of both mutex and thread. It makes more sense now... Or not?
The thread is still working with the data of object1, not object2, so it still doesn't make any sense. I may have moved the mutex, but the thread is unaware of object2. Or is it? I am unable to find the answer so I am asking you for help.
Am I doing something completely wrong and copying/moving threads and mutexes is just a bad design and I should rethink the architecture of the program?
There was a question about actual purpose of the class. It is actually a TCP/IP client (represented as a class) that holds:
More that one connection could be established at a time, so somewhere in a code there is a std::vector<Client>
field that represents all active connections.
Connections are determined by the configuration file.
//read configurations
...
//init clients
for (auto& configuration : _configurations)
{
Client client(configuration);
_activeClients.push_back(client); // this is where compiler reminded me that I am unable to move my object (aka WonnaBeMovedClass object).
}}
I've changed _activeClients
from std::vector<Client>
to std::vector<std::unique_ptr<Client>>
and modified initialization code to create pointer objects instead of objects directly and worked around my issues, but the question remained so I decided to post it here.
Upvotes: 2
Views: 935
Reputation: 120049
Let's break the issue in two.
std::function
stored somewhere or whatever) has the address of your data and can access it. This is actually very similar to the previous case, only instead of the OS it's your own code that holds on the data. The solution, as before, is in not moving your data. Store and move a (smart) pointer to the data instead.To summarise,
class WonnaBeMovedClass
{
public:
WonnaBeMovedClass
(WonnaBeMovedClass&& other);
void start();
private:
struct tdata {
std::vector<int> _sharedData;
std::thread _threadUpdate;
std::mutex _mutexShaderData;
};
std::shared_ptr<tdata> data;
static void updateSharedData(std::shared_ptr<tdata>);
};
void WonnaBeMovedClass::start()
{
_threadUpdate = std::thread(&updateSharedData, data);
}
Upvotes: 1
Reputation: 740
WonnaBeMovedClass is a handle holding thread and mutex, so it is not a bad design to provide them with a move semantics (but not copy).
The second solutions looks fine, but don't forget about proper resource management for your mutex (construct and destruct). I don't really undertand the real life purpose of the class, so depending on the whole solution desing, it might be better to use shared_ptr
instead of unique_ptr
(in case that multiple WonnaBeMovedClass can share same mutex).
std::thread
is itself a handle to system thread, so it doesn't have to be wrapped in a pointer, resource management (i.e OS thread handle) is managed by the standard library itself.
Note that mutex are actually kernel objects (usually implemented as an opaque pointer, for example in Windows API), and thus should not be modified or changed by user code in any way.
Upvotes: 0
Reputation: 73414
It makes more sense now... Or not?
Not really.
If an std::mutex
gets moved, the other threads will not be aware of the modification of memory address of that mutex! This discards thread safety.
However, a solution with std::unique_ptr
exists in Copy or Move Constructor for a class with a member std::mutex (or other non-copyable object)?
Last, but not least C++14 seems to have something to bring into the play. Read more in How should I deal with mutexes in movable types in C++?
Upvotes: 0