shared_instance implementation using C++ and RAII

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).

• Aren't singleton-like things usually a bad idea? What are you aiming for with this, I'm curious. – Blasco Jan 29 '18 at 12:58
• yeah they are. however, i need a kind of context-object: using raii to call c-style functions like create (in ctor) and destroy (in dtor). i need to make sure that that context exists as long as i'm using some ressources in that context. the problem is in situations like 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 – cubber Jan 29 '18 at 14:16
• so this is not really a classical singleton that is used to share an instance but rather to make sure that client-code can not accidentally misuse the library code and it remains exception-safe. if there are any other suggestions of how i could accomplish this, let me know, i'd be very happy. by the way: the returned shared_pointer is then hidden in the context-object which acts as kind of a contract meaning that the init/create/.. function was called and that it will be cleaned up extactly once when that context is not referenced anywhere anymore. – cubber Jan 29 '18 at 14:23

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.

• 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) :) – cubber Feb 8 '18 at 11:28
• 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! – cubber Feb 8 '18 at 11:31