Writing Singleton Classes

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • red floyd

    #16
    Re: Writing Singleton Classes

    Mark A. Gibbs wrote:[color=blue]
    >
    > DaKoadMunky wrote:
    >
    >[color=green]
    >> One of the respondents to this thread posted an example of the Double
    >> Checked
    >> Locking idiom.
    >>
    >> Apparently even that is problematic as discussed by Scott Meyers in the
    >> document @ http://www.nwcpp.org/Downloads/2004/DCLP_notes.pdf[/color]
    >
    >
    > am i missing something here? why wouldn't this work:
    >
    > Singleton* Singleton::Inst ance()
    > {
    > if (pinstance != 0)
    > return pinstance;
    >
    > Lock();
    >
    > if (pinstance == 0)
    > pinstance = makeSingleton() ;
    >
    > Unlock();
    >
    > return pinstance;
    > }
    >
    > Singleton* Singleton::make Singleton()
    > {
    > return new Singleton;
    > }
    >
    > mark
    >[/color]

    Andrei Alexandrescu discusses this in "Modern C++ Design". It has to do
    with memory architectures. In some cases, you need to do hardware
    specific stuff to ensure cache coherency. He also recommends making
    pinstance volatile.

    Comment

    • Mark A. Gibbs

      #17
      Re: Writing Singleton Classes


      red floyd wrote:
      [color=blue]
      > Andrei Alexandrescu discusses this in "Modern C++ Design". It has to do
      > with memory architectures. In some cases, you need to do hardware
      > specific stuff to ensure cache coherency. He also recommends making
      > pinstance volatile.[/color]

      I had to dig up my copy to check what you meant, and the only concern I
      could see addressed is based on architectures that queue up memory
      writes. Is that what you meant?

      In a case like this, couldn't you rewrite:

      Singleton* Singleton::make Singleton()
      {
      Singleton* temp = 0;

      try
      {
      ImplementationS pecificLock();
      Singleton* temp = new Singleton;
      ImplementationS pecificUnlock() ;
      }
      catch(...)
      {
      ImplementationS pecificUnlock() ;
      throw;
      }

      return temp;
      }

      for those implementations that require it?

      On a side note, how common are such architectures?

      Andrei Alexandrescu doesn't actually describe the type of singleton I
      use most often, which is kind of a variation on the phoenix singleton.
      I'm not sure if the pattern I use has a name, but it requires explicit
      lifetime control, allows for optional construction (lazy instantiation)
      and external locking and checking.

      template <typename T>
      class Singleton
      {
      public:
      typedef implementation_ defined Mutex;

      static Mutex& GetMutex() { return mutex_; }

      static bool InstanceExists( ) { return !(instance_ = 0); }

      static T& Instance()
      {
      if (!InstanceExist s()) throw SingletonDoesNo tExistException ;
      return *instance_;
      }

      protected:
      explicit Singleton(T* t = 0)
      {
      Mutex::Lock lock(GetMutex() );

      if (instance_) throw MultipleSinglet onException;

      instance_ = t ? t : static_cast<T*> (this);
      }

      ~Singleton()
      {
      Mutex::Lock lock(GetMutex() );

      instance_ = 0;
      }

      private:
      static Mutex mutex_;
      static T* instance_;
      };

      And used like this:

      class Foo : public Singleton<Foo>
      {
      public:
      void func1();
      void func2();
      };

      The thinking I've always used behind this design is that I'd like to be
      responsible for when the mutex is locked and unlocked, although
      individual functions are free to lock it just in case (multiple locks
      within a thread are ok, but each lock must be matched by an unlock). I'd
      also like to be able to control when and how the singleton is created,
      and potentially replace it on the fly in some cases.

      For example:

      // In this case, I use Foo if it is available
      Foo::Mutex::Loc k lock(Foo::GetMu tex());
      if (Foo::InstanceE xists()) { /*use it*/ }

      // I can also call multiple functions without relocking
      Foo::Mutex::Loc k lock(Foo::GetMu tex());
      Foo& foo = Foo::Instance() ;
      foo.func1();
      foo.func2();

      // And I can swap objects on the fly
      class Base : public Singleton<Base>
      {
      public:
      virtual ~Base();

      virtual void func1() = 0;
      virtual void func2() = 0;

      protected:
      Base();
      };

      class Derived1 : public Base
      {
      public:
      void func1();
      void func2();
      };

      class Derived2 : public Base
      {
      public:
      void func1();
      void func2();
      };

      Base::Mutex::Lo ck lock(Base::GetM utex());
      Base* base = &Base::Instance (); // Currently a Derived1
      delete base;
      base = new Derived2;
      Base& new_base = &Base::Instance ();
      new_base.func1( );
      new_base.func2( );

      This doesn't seem to suffer from any instruction ordering problem to me,
      and the cached-memory-write architecture problem could be handled in the
      mutex class. How does this measure up?

      mark

      Comment

      Working...