I'm currently working on a code where I'll need to statically share an instance of an object between multiple parts of my code. So basically a singleton. This instance will be shared until no one uses it anymore. At this point I need to perform some cleanup operation before deleting the actual instance.
If someone asks for the shared instance after this point, it will get a newly created one and so on.
So I wrote something like this:
template<typename F>
struct static_filter
{
template<typename ...Args>
static std::shared_ptr<F> get(Args... a)
{
return _inst = _inst ?: std::shared_ptr<F>(new F(a...));
}
static void cleanup()
{
// some cleanup operation over inst
_inst.reset();
}
private:
static std::shared_ptr<F> _inst;
};
template<typename F> std::shared_ptr<F> static_filter<F>::_inst{nullptr};
And now I'm looking in a way to automatically detect when nobody is using _inst anymore. Basically, I'd like to get a callback each time the use_count() of _inst falls to 1. At this point I would be able to cleanup & reset.
I'd like to avoid implementing my own reference counting for this (the application is using shared_ptr everywhere and it will be kinda tiresome to change to a custom type).
I tried using a custom deleter on my instance, something along the lines of:
template<typename F>
struct static_filter
{
template<typename ...Args>
static std::shared_ptr<F> get(Args... a)
{
return _inst = _inst ?: std::shared_ptr<F>(new F(a..., **static_filter<F>::cleanup**));
}
static void cleanup()
{
// some cleanup operation over inst
_inst.reset();
}
private:
static std::shared_ptr<F> _inst;
};
template<typename F> std::shared_ptr<F> static_filter<F>::_inst{nullptr};
But obviously this did not work as my ref counter never did actually reach 0.
Does anyone knows if there is a way to achieve this using shared_ptr ?
Thanks.
Your use of shared_ptr
here is wrong on a semantic level. You do not want to hold ownership in this static
variable but only observe through it. This is the use case for std::weak_ptr
. With this a static variable you can observe the object, return it as a shared_ptr
if it exists and do not interefere with its destruction, when others hold a reference to it.
This would look as follows:
template<typename F>
struct static_filter
{
template<typename ...Args>
static std::shared_ptr<F> get(Args... a)
{
// Returns the shared_ptr observed, if it exists and is not expired, empty ptr otherwise
// is atomic
auto shared = _inst.lock();
if(!shared) {
_inst = (shared = std::shared_ptr<F>(new F(a..., **static_filter<F>::cleanup**));
}
return shared;
}
static void cleanup()
{
// Always check if the calling object is really the managed object
// some cleanup operation over inst
_inst.reset();
}
private:
static std::weak_ptr<F> _inst;
};
template<typename F> std::weak_ptr<F> static_filter<F>::_inst{};
Note that this is not thread safe (your code before was not thread safe, either), since two simultaneous calls to get
when the pointer is empty, can both see that the pointer is empty and both construct a new object of type F
. You will have to add an std::lock_guard
to avoid this.
Note that even if you could add a callback on use_count == 1
, this would be inherently unsafe. This is why std::shared_ptr::unique()
is deprecated from C++17 onwards and is removed in C++20 (see here and here)