I created a little singleton
-like class that gets deleted when there is no reference to it anymore (so its lifetime shall be somewhat predictable). I aimed to make it thread-safe.
#include <memory>
#include <mutex>
template< typename t_type >
struct shared_instance
{
static std::shared_ptr< t_type > get_shared_instance()
{
static std::weak_ptr< t_type > weak;
static std::mutex mutex;
auto result = weak.lock();
if( nullptr == result )
{
std::unique_lock< std::mutex > lock{ mutex };
result = weak.lock();
if( nullptr != result )
{
return result;
}
result = std::make_shared< t_type >();
weak = result;
}
return result;
}
};
template< typename t >
struct context
{
context()
: instance{ shared_instance< t >::get_shared_instance() }
{ }
private: std::shared_ptr< t > instance;
};
I would be glad if you could comment on the implementation itself and how I could tackle non-default-constructible objects (I know of perfect forwarding and variadic templates but I have not found an elegant way to evaluate them lazily).
1 Answer 1
Non-Default Constructors:
Adding support for non-default constructible objects in a "lazy" fashion, could be done by adding a factory function of std::function<std::shared_ptr< t_type >()>
.
template< typename t_type >
struct shared_instance
{
using t_factory = std::function<std::shared_ptr< t_type >()>;
static t_factory factory; // MUST be set before calling get_shared_instance
static std::shared_ptr< t_type > get_shared_instance()
{
...
assert(factory); // or throw, or return empty shared_ptr
result = factory(); // instead of make_shared
...
}
};
// and define the static member...
template< typename t_type >
typename shared_instance< t_type >::t_factory shared_instance< t_type >::factory;
...
int main()
{
context<int> c; // not ok... haven't set factory yet!
// do this before any call to get_shared_instance
shared_instance<int>::factory = [] () { return std::make_shared<int>(5); }; // store things in the lambda by value, not reference...
context<int> c; // ok
}
I'm not sure this counts as elegant though, as it gives you a two-step initialization process. This could be fixed by recognising the shared_instance itself as a kind of factory, making its contents non-static, and passing it into the contexts:
template< typename t >
struct context
{
context(instance_provider< t >& provider): // now it can't be created unless this exists...
instance{ provider.get_shared_instance() }
{ }
private:
std::shared_ptr< t > instance;
};
int main()
{
auto provider = instance_provider<int>([] () { return std::make_shared<int>(5); });
context<int> c(provider);
}
Further Considerations:
Does the C library you're using say anything about context lifetime itself? (e.g. Winsock reference counts calls to WSAStartup() and WSACleanup() internally, so as long as the calls are paired properly the C library handles cleaning up)
The main feature that this shared_instance class seems to provide is destroying The Instance promptly after the last context is destroyed. RAII itself could be achieved by just storing a unique pointer over the maximum possible lifetime of any contexts, which would be simpler. Is this prompt destruction actually important?
If not, then it's just a singleton, which we either live with, or do the work to pass a pointer or reference to the thing you need where it needs to be.
-
\$\begingroup\$ if i understood your second approach correctly, i could just create context<> c1{provider} and context<> c2{provider} which would both call the ctor of the context-type and the dtor of the context-type, so it might happen that objects have a context-member created from c1 while c2 already called the teardown-function, right? passing in a lambda for lazy execution is a good idea when retreiving the parameters of the lambda is more expensive than the whole concept itself, setting the required params statically before calls to get_shared_instance() is propably cheapest (and the ugiest one) :) \$\endgroup\$cubber– cubber2018年02月08日 11:28:40 +00:00Commented Feb 8, 2018 at 11:28
-
\$\begingroup\$ i'm still marking your answer as accepted because of your efforts and additional input in further considerations. after all, my question was how to decorate an anti-pattern which can not be done much nicer than you suggested i suppse, thanks again for all your effort and considerations! \$\endgroup\$cubber– cubber2018年02月08日 11:31:15 +00:00Commented Feb 8, 2018 at 11:31
Explore related questions
See similar questions with these tags.
context c1{}; /*some code here*/; context c2{};
c2 will tear down the context but it c1 should (braoder scope, calling the create-function mutliple times is not an error from the c-library's point of view). i need a way to make sure that behind the scenes there is only one call to destroy in the dtor of the last instance \$\endgroup\$