Reputation: 7582
There is a static shared_ptr<MyClass> get()
that has a weak_ptr
inside that gives away shared pointers using ptr.lock()
.
When ptr.lock()
gives an empty pointer, the singleton should be created again.
But does it guarantee (it doesn't) that the destructor of the previous singleton has completed? What can be done about that?
Upvotes: 2
Views: 305
Reputation: 473192
What can be done about that? Stop using terrible programming styles.
If you're going to use a singleton, then it should be a singleton: one instance, period. There's no need for managing its lifetime with smart pointers; it's always there. What's the point of destroying it only to recreate it later? Especially if the recreation function isn't given special parameters to recreate it later?
However, to your question:
does it guarantee (it doesn't) that the destructor of the previous singleton has completed
Does it matter? In order for the object's destructor to be started, the count of shared_ptr
references to the object must be zero. So the weak_ptr
is already empty. An object's lifetime ends when its destructor starts (just as an object's lifetime begins when its constructor completes). So the singleton itself is already destroyed; you're just doing cleanup work.
So there is no problem with creating a new instance of the singleton within the callstack of the old instance's destructor. It simply won't be accessing itself.
In a multithreaded environment, this kind of interface is already terribly broken without some kind of lock within the get
function that returns/creates the singleton. Without such mutual exclusion, multiple threads could try to create the singleton simultaneously, which could cause the construction of multiple singleton instances.
As for resources within the singleton itself, the release of such resources has to be governed by some form of mutual exclusion mechanism already. The only time when a resource is itself a singleton. But unlike the singleton we're talking about, it would be one that cannot be owned by multiple pieces of code.
In that case, your singleton shouldn't have ownership of that resource at all. It can reference it, but it cannot destroy or create it.
Upvotes: 0
Reputation: 69854
But does it guarantee (it doesn't) that the destructor of the previous singleton has completed? What can be done about that?
It's an unusual request, but I can see how it might be necessary if you're controlling an external singleton resource.
here's my solution.
make sure you check it thoroughly before using it in production
#include <memory>
#include <mutex>
#include <condition_variable>
struct tricky_object
{
};
class tricky_cache
{
struct statics {
std::mutex _m;
std::condition_variable _deleted;
bool _exists = false;
std::weak_ptr<tricky_object> _cache;
};
static statics& get() {
static statics _;
return _;
}
public:
static
std::shared_ptr<tricky_object> acquire()
{
// get static data
auto& data = get();
// lock the cache's mutex
auto lock = std::unique_lock<std::mutex>(data._m);
std::shared_ptr<tricky_object> candidate;
// wait on the condition variable for the following conditions to be true:
data._deleted.wait(lock, [&data, &candidate] {
// either the object is in play and we have acquired another reference...
candidate = data._cache.lock();
if (candidate)
return true;
// ... or (if not) the previous object is actually dead and buried.
return !data._exists;
});
// at this point we still own the lock and wait must have returned true, so...
// if we own the candidate then it was already in play
if (candidate)
return candidate;
// otherwise the previous object is certainly destroyed and we may create another
data._cache = candidate = std::shared_ptr<tricky_object>(new tricky_object(),
[&data](tricky_object*p) {
// but the custom deleter needs some trickery
delete p;
if (p) {
auto lock = std::unique_lock<std::mutex>(data._m);
data._exists = false;
lock.unlock();
data._deleted.notify_all();
}
});
// and we should record the fact that the object now exists...
data._exists = true;
lock.unlock();
// ... and inform all waiters that they may continue acquiring
data._deleted.notify_all();
return candidate;
}
};
int main()
{
auto p = tricky_cache::acquire();
return 0;
}
Upvotes: 1