Looking at this implementation of std::shared_ptr https://thecandcppclub.com/deepeshmenon/chapter-10-shared-pointers-and-atomics-in-c-an-introduction/781/ :
Question 1 : I can see that we're using std::atomic<int*>
to store the pointer to the reference count associated with the resource being managed. Now, in the destructor of the shared_ptr, we're changing the value of the ref-count itself (like --(*reference_count)
). Similarly, when we make a copy of the shared_ptr, we increment the ref-count value. However, in both these operations, we're not changing the value of the pointer to the ref-count but rather ref-count itself. Since the pointer to ref-count is the "atomic thing" here, I was wondering how would ++ / -- operations to the ref-count be thread-safe? Is std::atomic
implemented internally in a way such that in case of pointers, it ensures changes to the underlying object itself are also thread-safe?
Question 2 : Do we really need this nullptr check in default_deleter
class before calling delete
on ptr? As per Is it safe to delete a NULL pointer?, it is harmless to call delete
on nullptr.
Question 1:
The implementation linked to is not thread-safe at all. You are correct that the shared reference counter should be atomic, not pointers to it. std::atomic<int*>
here makes no sense.
Note that just changing std::atomic<int*>
to std::atomic<int>*
won't be enough to fix this either. For example the destructor is decrementing the reference count and checking it against 0
non-atomically. So another thread could get in between these two operations and then they will both think that they should delete the object causing undefined behavior.
As mentioned by @fabian in the comments, it is also far from a correct non-thread-safe shared pointer implementation. For example with the test case
{
Shared_ptr<int> a(new int);
Shared_ptr<int> b(new int);
b = a;
}
it will leak the second allocation. So it doesn't even do the basics correctly.
Even more, in the simple test case
{
Shared_ptr<int> a(new int);
}
it leaks the allocated memory for the reference counter (which it always leaks).
Question 2:
There is no reason to have a null pointer check there except to avoid printing the message. In fact, if we want to adhere to the standard's specification of std::default_delete
for default_deleter
, then at best it is wrong to check for nullptr
, since that is specified to call delete
unconditionally.
But the only possible edge case where this could matter is if a custom operator delete
would be called that causes some side effect for a null pointer argument. However, it is anyway unspecified whether delete
will call operator delete
if passed a null pointer, so that's not practically relevant either.