ReferenceCountedSingleton


#1

I couldn’t help but notice a new function-scoped variable with static storage duration:

    static AudioSessionHolder& getSessionHolder()
    {
        static AudioSessionHolder audioSessionHolder;
        return audioSessionHolder;
    }

We could do away with the entire class of problems having to do with destruction of objects with static storage duration (such as the one I described with the threads) by encapsulating that implementation in a “ReferenceCountedSingleton” and using that from now on, instead of static variables in functions:

class AudioSessionHolder : public ReferenceCountedSingleton <AudioSessionHolder>
...

The class ReferenceCountedSingleton implementation would be similar to what is in juce_Thread.cpp now, but generalized.

Also, ReferenceCountedSingleton could be a useful pattern (I would use it). It would be pretty handy. For example, to wrap up a DynamicLibrary used by other reference counted objects.


#2

Yeah, I was thinking along those lines while I was writing the threads thing, but didn’t have time to fully think it through.


#3

No rush but something to think about. My personal preference is for CRTP (Curiously Recurring Template Pattern). I remember the old Juce singleton implementation…with the macros…yuck! This is what I use:

// Thread-safe singleton which comes into existence on first use.
// All objects with static storage duration should, in general, be singletons,
// or else they may not be leak-checked correctly.
//
// class Object must provide this function:
//
//  Object* Object::createInstance()
//

//------------------------------------------------------------------------------

// "base classes dependent on a template parameter aren't part of lookup." - ville

class SingletonLifetime
{
public:
  enum Lifetime
  {
    // Singleton is created on first use and destroyed when
    // the last reference is removed.
    //
    createOnDemand,

    // Like createOnDemand, but after the Singleton is destroyed an
    // exception will be thrown if an attempt is made to create it again.
    //
    createOnDemandOnce,

    // The singleton is created on first use and persists until program exit.
    //
    persistAfterCreation
  };
};

template <class Object>
class SharedSingleton : private PerformedAtExit
{
protected:
  explicit SharedSingleton (SingletonLifetime::Lifetime const lifetime)
    : PerformedAtExit (lifetime == SingletonLifetime::persistAfterCreation)
    , m_lifetime (lifetime)
  {
    vfassert (s_instance == nullptr);

    if (m_lifetime == SingletonLifetime::persistAfterCreation)
    {
      incReferenceCount ();
    }
    else if (m_lifetime == SingletonLifetime::createOnDemandOnce && *s_created)
    {
      Throw (Error().fail (__FILE__, __LINE__));
    }

    *s_created = true;
  }

  virtual ~SharedSingleton ()
  {
    vfassert (s_instance == nullptr);
  }

public:
  typedef SharedObjectPtr <Object> Ptr;

  static Ptr getInstance ()
  {
    Ptr instance;

    instance = s_instance;

    if (instance == nullptr)
    {
      SpinLock::ScopedLockType lock (*s_mutex);

      instance = s_instance;
  
      if (instance == nullptr)
      {
        s_instance = Object::createInstance ();

        instance = s_instance;
      }
    }

    return instance;
  }

  inline void incReferenceCount() noexcept
  {
    m_refs.addref ();
  }

  inline void decReferenceCount() noexcept
  {
    vfassert (m_refs.is_signaled ());

    if (m_refs.release ())
      destroySingleton ();
  }

  // Caller must synchronize.
  inline bool isBeingReferenced () const
  {
    return m_refs.is_signaled ();
  }

private:
  void performAtExit ()
  {
    decReferenceCount ();
  }

  void destroySingleton ()
  {
    bool destroy;

    {
      SpinLock::ScopedLockType lock (*s_mutex);

      if (isBeingReferenced ())
      {
        destroy = false;
      }
      else
      {
        destroy = true;
        s_instance = 0;
      }
    }

    if (destroy)
    {
      delete this;
    }
  }

private:
  SingletonLifetime::Lifetime const m_lifetime;
  Atomic::Counter m_refs;

private:
  static Object* s_instance;
  static Static::Storage <SpinLock, SharedSingleton <Object> > s_mutex;
  static Static::Storage <bool, SharedSingleton <Object> > s_created;
};

With the template, I make it a requirement that the function “createSingleton()” must be provided. The alternative is to put “new Object” directly in the Singleton template but if you do that, it forces you to expose the implementation of the client object. By making createSingleton() user-provided, the implementation can be hidden in the source file which is quite handy if the singleton uses platform specifics.