HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

C++ shared_singleton

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
shared_singletonstackoverflowprogramming

Problem

I actually feel bad posting "yet another singleton"... I wrote the following one many years ago and had recently found another application for it. We had many threads, each running the same function that requires the use of a boost::asio::io_service instance. It was best that all threads shared the same io_service instance and also that that instance be destroyed before main returned. That last requirement (whose validity I now question, but, whatever..) meant no global or static object.

Here's a function that returns an instance of an object wrapped in a shared_ptr. What's special about it though is that we keep an additional reference count so the singleton is eagerly destructed when the last shared_ptr is gone.

For example, if you call the function twice, you have two shared_ptrs, each with a ref-count of 1, but our singleton's ref-count is 2. On the other hand, if you call the function only once and copy the returned shared_ptr, the shared_ptr has a ref-count of 2 but our singleton's ref-count is at one. Either way, the singleton instance gets destructed when the last shared_ptr is destructed.

One disadvantage of this code is that the class is instantiated from its default constructor.

#include 
#include 

// Deleter function given to the shared_ptr returned by get_shared_singleton.
template
void release_shared(std::mutex& m, int& n, T*& p)
{
    std::lock_guard lg(m);
    if(!--n)
    {
        delete p;
        p = 0;
    }
}

template
std::shared_ptr get_shared_singleton()
{
    static std::mutex m;

    std::lock_guard lg(m);

    static int n = 0;   // Ref count.
    static T* p = 0;    

    if(!p) p = new T();
    ++n;

    return std::shared_ptr(p, std::bind(release_shared, std::ref(m), std::ref(n), std::ref(p)));
}


The requirements, more clearly stated:

  • You wrote a function foo() that requires an instance of object X to perform its duty.



  • foo() can be invoked concurrently from different threads.



  • Concurrent `foo()

Solution

You can use a static weak_ptr in the function to cache the shared instance without extending its lifetime:

template
std::shared_ptr get_shared_instance()
{
    static std::mutex m;
    static std::weak_ptr cache;

    std::lock_guard lg(m);

    std::shared_ptr shared = cache.lock();
    if (cache.expired()) {
        shared.reset(new T);
        cache = shared;
    }
    return shared;
}


So, the first caller will find a default-initialized weak_ptr counts as expired, and construct a new T.

While the first caller is active (and keeping its new T alive), concurrent callers will get the same object, cached in the weak_ptr. When the last concurrent user is done, the object will be destroyed.

A subsequent caller will find the weak_ptr expired (same as before the first call), and create a new T ... etc. etc.

Code Snippets

template<typename T>
std::shared_ptr<T> get_shared_instance()
{
    static std::mutex m;
    static std::weak_ptr<T> cache;

    std::lock_guard<std::mutex> lg(m);

    std::shared_ptr<T> shared = cache.lock();
    if (cache.expired()) {
        shared.reset(new T);
        cache = shared;
    }
    return shared;
}

Context

StackExchange Code Review Q#14343, answer score: 7

Revisions (0)

No revisions yet.