So I'm trying to pass a shared pointer between threads via a messaging mechanism that I'm using. Due to how the serialization/deserialization works, I cannot directly embed a shared_ptr into a message that I send. So I effectively need to send a raw pointer of a shared_ptr. See below:
Thread 1:
auto objPtr = std::make_shared<ObjectClass>();
uint64_t serializedPtr = reinterpret_cast<uint64_t>(&objPtr);
Thread 2:
std::shared_ptr<ObjectClass> objPtrT2 = *(reinterpret_cast<std::shared_ptr<ObjectClass>*>(serializedPtr));
This sometimes crashes in thread 2 when it goes to increment the reference count of the shared pointer. I can only assume some race condition is at play here but haven't been able to sort out a solution. Note that it doesn't always crash and the deserialization is seemingly always successful.
Do I need to synchronize access to this shared_ptr (the shared_ptr itself, not what the share_ptr points to)? I'm concerned that the manner in which I am transferring this shared_ptr is breaking how the reference count is being managed.
I'm still debating whether the use of a shared_ptr is appropriate here for other performance related reasons but I'd like to know what I'm doing wrong for my own benefit.
Thanks
EDIT: Just to note, thread 1 and thread 2 are in the same process/host. I am emplacing the shared_ptr into a map managed by thread1 (I tried to leave out details that I initially thought to be unimportant). What I didn't realize, however, was that the manner in which I retrieve from said map was incorrect. I was copying the contents of the map into a temp shared_ptr and then sending the address of the temp shared_ptr to thread2. So I was unwittingly sending over the address of a variable on the stack. Silly mistake, but I think the commentary in this thread has been quite instructive/helpful nonetheless. The following seems to fix my issue.
auto& objPtr = m_objMap[index];
uint64_t serializedPtr = reinterpret_cast<uint64_t>(&objPtr);
shared_ptr
automatically increases and decreases its internally-stored reference count when you copy it (using the assignment operator=
), and - importantly - when a shared_ptr
is destroyed (by going out of scope). Your approach for transmitting a pointer-to-shared-pointer is fundamentally flawed in the code above, because you are transmitting the address of a temporary shared pointer, but not the ownership or lifespan. The shared_ptr
- which is still owned by Thread A - might go out of scope and be destroyed before Thread B can use it.
In order to transfer ownership of a shared_ptr instance, I would suggest creating a heap-allocated/dynamic shared_ptr
for transfer. This can be accomplished using new
or (even better) make_unique
. Using a unique_ptr (i.e.: unique_ptr<shared_ptr<ObjectClass>>
), you would use the 'release' method, before passing the pointer across the thread barrier in your message.
Thread A:
auto sharedPtr = std::make_shared<ObjectClass>();
// This line creates a heap-allocated copy of a
// shared_ptr (incrementing reference count)
// And places ownership inside a unique_ptr
auto sharedPtrDynamicCopy = std::make_unique<decltype(sharedPtr)>(sharedPtr);
// This 'releases' ownership of the heap-allocated shared_ptr,
// returning a raw pointer; it is now a potential
// memory leak!!! It must be 'captured' in Thread B.
auto rawPtrToPass = sharedPtrDynamicCopy.release();
Thread B:
// Here, we immediately 'capture' the raw pointer back
// inside a unique_ptr, closing the loop on the potential
// memory leak
auto sharedPtrDynamicCopy = unique_ptr<shared_ptr<ObjectClass>>(rawPtrFromThreadA);
// Now we can make a copy of the shared_ptr, if we like.
// This sharedCopy will live on, even after recvdPtr goes
// out of scope.
auto sharedCopy = *sharedPtrDynamicCopy;
You could perhaps shorten this further by simply 'new'-ing a raw shared_ptr
instead of capturing it inside a unique_ptr<shared_ptr<T>>
, but I personally prefer this approach since it has clear "capture" and "release" semantics for the pointer-in-flight.