C ++ синглтон шаблон класса - PullRequest
3 голосов
/ 10 августа 2010

В недавнем проекте мне пришлось создать класс Singleton, и после долгих поисков в Google я придумал это определение класса шаблона.Идея состоит в том, чтобы наследовать от этого шаблонного класса и сделать конструктор производного класса защищенным / закрытым.Кажется, это работает хорошо, но я использовал его только с одним классом в одном проекте, поэтому я надеялся, что некоторые из вас могут указать, если я допустил ошибки в реализации.Вот оно:

/**
 * @brief 
 *    Singleton design pattern implementation using a dynamically allocated singleton instance.
 *
 * The SingletonDynamic class is intended for use as a base for classes implementing the Singleton
 * design pattern and require lazy initialization of the singleton object. The default 
 * implementation is not thread-safe, however, the derived classes can make it so by reinitializing
 * the function pointers SingletonDynamic<T>::pfnLockMutex, SingletonDynamic<T>::pfnUnlockMutex
 * and SingletonDynamic<T>::pfnMemoryBarrier. The member function pointers are initialized by 
 * default to point to placeholder functions that do not perform any function. The derived class
 * must provide alternate implementations for SingletonDynamic<T>::lock_mutex(),
 * SingletonDynamic<T>::unlock_mutex() and SingletonDynamic<T>::memory_barrier() respectively
 * and reinitialize the respective function pointer members to these alternate implementations.
 *
 * @tparam T
 *    The type name of the derived (singleton) class
 *
 * @note The derived class must have a no-throw default constructor and a no-throw destructor.
 * @note The derived class must list this class as a friend, since, by necessity, the derived class'
 *       constructors must be protected / private.
 */
template< typename T >
class SingletonDynamic
{
public:
  /**
   * Factory function for vending mutable references to the sole instance of the singleton object.
   *
   * @return A mutable reference to the one and only instance of the singleton object.
   */
  static T &instance()
  {
    return *SingletonDynamic< T >::get_instance();
  }


  /**
   * Factory function for vending constant references to the sole instance of the singleton object.
   *
   * @return A constant reference to the one and only instance of the singleton object.
   */
  static const T &const_instance()
  {
    return *SingletonDynamic< T >::get_instance();
  }

protected:
  /** Default constructor */
  SingletonDynamic() {}

  /** Destructor */
  virtual ~SingletonDynamic() 
  {
    delete SingletonDynamic< T >::pInstance_;
  }

  /** Defines an alias for a function pointer type for executing functions related to thread-safety */
  typedef void(*coherence_callback_type)();

  /** 
   * Pointer to a function that will lock a mutex denying access to threads other that the current 
   * 
   * @note The function must have the signature void foo()
   * @note The derived class must never set this variable to NULL, doing so will cause a crash. The 
   *       default value must be left unchanged if this functionality is not desired.
   */
  static coherence_callback_type  pfnLockMutex;

  /** 
   * Pointer to a function that will unlock a mutex allowing access to other threads 
   * 
   * @note The function must have the signature void foo()
   * @note The derived class must never set this variable to NULL, doing so will cause a crash. The 
   *       default value must be left unchanged if this functionality is not desired.
   */
  static coherence_callback_type  pfnUnlockMutex;

  /** 
   * Pointer to a function that executes a memory barrier instruction that prevents the compiler
   * from reordering reads and writes across this boundary.
   * 
   * @note The function must have the signature void foo()
   * @note The derived class must never set this variable to NULL, doing so will cause a crash. The 
   *       default value must be left unchanged if this functionality is not desired.
   */
  static coherence_callback_type  pfnMemoryBarrier;

private:
  /** The sole instance of the singleton object */
  static T *pInstance_;

  /** Flag indicating whether the singleton object has been created */
  static volatile bool flag_;

  /** Private copy constructor to prevent copy construction */
  SingletonDynamic( SingletonDynamic const & );

  /** Private operator to prevent assignment */
  SingletonDynamic &operator=( SingletonDynamic const & );


  /** 
   * Fetches a pointer to the singleton object, after creating it if necessary
   *
   * @return A pointer to the one and only instance of the singleton object.
   */
  static T *get_instance()
  {
    if( SingletonDynamic< T >::flag_ == false ) {
      /* acquire lock */
      (*SingletonDynamic< T >::pfnLockMutex)();

      if( SingletonDynamic< T >::pInstance_ == NULL ) {
        pInstance_ = new T();
      }

      /* release lock */
      (*SingletonDynamic< T >::pfnUnlockMutex)();

      /* enforce all prior I/O to be completed */
      (*SingletonDynamic< T >::pfnMemoryBarrier)();

      SingletonDynamic< T >::flag_ = true;

      return SingletonDynamic< T >::pInstance_;
    } else {
      /* enforce all prior I/O to be completed */
      (*SingletonDynamic< T >::pfnMemoryBarrier)();

      return SingletonDynamic< T >::pInstance_;
    }
  }


  /**
   * Placeholder function for locking a mutex, thereby preventing access to other threads. This 
   * default implementation does not perform any function, the derived class must provide an 
   * implementation if this functionality is desired.
   */
  inline static void lock_mutex()
  {
    /* default implementation does nothing */
    return;
  }


  /**
   * Placeholder function for unlocking a mutex, thereby allowing access to other threads. This 
   * default implementation does not perform any function, the derived class must provide an 
   * implementation if this functionality is desired.
   */
  inline static void unlock_mutex()
  {
    /* default implementation does nothing */
    return;
  }


  /**
   * Placeholder function for executing a memory barrier instruction, thereby preventing the 
   * compiler from reordering read and writes across this boundary. This default implementation does 
   * not perform any function, the derived class must provide an implementation if this 
   * functionality is desired.
   */
  inline static void memory_barrier()
  {
    /* default implementation does nothing */
    return;
  }
};

/* Initialize the singleton instance pointer */
template< typename T >
T *SingletonDynamic<T>::pInstance_        = NULL;

/* Initialize the singleton flag */
template< typename T >
volatile bool SingletonDynamic<T>::flag_  = false;

/* Initialize the function pointer that locks the mutex */
template< typename T >
typename SingletonDynamic<T>::coherence_callback_type SingletonDynamic<T>::pfnLockMutex  
                                                              = &SingletonDynamic<T>::lock_mutex;

/* Initialize the function pointer that unlocks the mutex */
template< typename T >
typename SingletonDynamic<T>::coherence_callback_type SingletonDynamic<T>::pfnUnlockMutex  
                                                              = &SingletonDynamic<T>::unlock_mutex;

/* Initialize the function pointer that executes the memory barrier instruction */
template< typename T >
typename SingletonDynamic<T>::coherence_callback_type SingletonDynamic<T>::pfnMemoryBarrier
                                                              = &SingletonDynamic<T>::memory_barrier;

Меня особенно беспокоит инициализация статического члена в файле заголовка и то, не вызовет ли это множественные ошибки определения, если включен файл заголовка класса, производного от SingleDynamicв нескольких файлах.Я уже попробовал это, и это, кажется, работает, но я не могу понять, почему это работает:).

Заранее спасибо, Ашиш.

РЕДАКТИРОВАТЬ: Модифицированная реализация с использованием дизайна, основанного на политике, как предложено в принятом решении.

/**
 * This is the default ConcurrencyPolicy implementation for the SingletonDynamic class. This 
 * implementation does not provide thread-safety and is merely a placeholder. Classes deriving from
 * SingletonDynamic must provide alternate ConcurrencyPolicy implementations if thread-safety is
 * desired.
 */
struct DefaultSingletonConcurrencyPolicy
{
  /**
   * Placeholder function for locking a mutex, thereby preventing access to other threads. This 
   * default implementation does not perform any function, the derived class must provide an 
   * alternate implementation if this functionality is desired.
   */
  static void lock_mutex() 
  { 
    /* default implementation does nothing */
    return;
  }

  /**
   * Placeholder function for unlocking a mutex, thereby allowing access to other threads. This 
   * default implementation does not perform any function, the derived class must provide an 
   * alternate implementation if this functionality is desired.
   */
  static void unlock_mutex()
  {
    /* default implementation does nothing */
    return;
  }

  /**
   * Placeholder function for executing a memory barrier instruction, thereby preventing the 
   * compiler from reordering read and writes across this boundary. This default implementation does 
   * not perform any function, the derived class must provide an alternate implementation if this 
   * functionality is desired.
   */
  static void memory_barrier()
  {
    /* default implementation does nothing */
    return;
  }
};


/**
 * @brief 
 *    Singleton design pattern implementation using a dynamically allocated singleton instance.
 *
 * The SingletonDynamic class is intended for use as a base for classes implementing the Singleton
 * design pattern and that dynamic allocation of the singleton object. The default implementation 
 * is not thread-safe; however, the class uses a policy-based design pattern that allows the derived 
 * classes to achieve threaad-safety by providing an alternate implementation of the 
 * ConcurrencyPolicy.
 *
 * @tparam T
 *    The type name of the derived (singleton) class
 * @tparam ConcurrencyPolicy
 *    The policy implementation for providing thread-safety
 *
 * @note The derived class must have a no-throw default constructor and a no-throw destructor.
 * @note The derived class must list this class as a friend, since, by necessity, the derived class'
 *       constructors must be protected / private.
 */
template< typename T, typename ConcurrencyPolicy = DefaultSingletonConcurrencyPolicy >
class SingletonDynamic : public ConcurrencyPolicy
{
public:
  /**
   * Factory function for vending mutable references to the sole instance of the singleton object.
   *
   * @return A mutable reference to the one and only instance of the singleton object.
   */
  static T &instance()
  {
    return *SingletonDynamic< T, ConcurrencyPolicy >::get_instance();
  }


  /**
   * Factory function for vending constant references to the sole instance of the singleton object.
   *
   * @return A constant reference to the one and only instance of the singleton object.
   */
  static const T &const_instance()
  {
    return *SingletonDynamic< T, ConcurrencyPolicy >::get_instance();
  }

protected:
  /** Default constructor */
  SingletonDynamic() {}

  /** Destructor */
  virtual ~SingletonDynamic() 
  {
    delete SingletonDynamic< T, ConcurrencyPolicy >::pInstance_;
  }

private:
  /** The sole instance of the singleton object */
  static T *pInstance_;

  /** Flag indicating whether the singleton object has been created */
  static volatile bool flag_;

  /** Private copy constructor to prevent copy construction */
  SingletonDynamic( SingletonDynamic const & );

  /** Private operator to prevent assignment */
  SingletonDynamic &operator=( SingletonDynamic const & );


  /** 
   * Fetches a pointer to the singleton object, after creating it if necessary
   *
   * @return A pointer to the one and only instance of the singleton object.
   */
  static T *get_instance()
  {
    if( SingletonDynamic< T, ConcurrencyPolicy >::flag_ == false ) {
      /* acquire lock */
      ConcurrencyPolicy::lock_mutex();

      /* create the singleton object if this is the first time */
      if( SingletonDynamic< T, ConcurrencyPolicy >::pInstance_ == NULL ) {
        pInstance_ = new T();
      }

      /* release lock */
      ConcurrencyPolicy::unlock_mutex();

      /* enforce all prior I/O to be completed */
      ConcurrencyPolicy::memory_barrier();

      /* set flag to indicate singleton has been created */
      SingletonDynamic< T, ConcurrencyPolicy >::flag_ = true;

      return SingletonDynamic< T, ConcurrencyPolicy >::pInstance_;
    } else {
      /* enforce all prior I/O to be completed */
      ConcurrencyPolicy::memory_barrier();

      return SingletonDynamic< T, ConcurrencyPolicy >::pInstance_;
    }
  }
};

/* Initialize the singleton instance pointer */
template< typename T, typename ConcurrencyPolicy >
T *SingletonDynamic< T , ConcurrencyPolicy >::pInstance_        = NULL;

/* Initialize the singleton flag */
template< typename T, typename ConcurrencyPolicy >
volatile bool SingletonDynamic< T , ConcurrencyPolicy >::flag_  = false;

Ответы [ 4 ]

3 голосов
/ 10 августа 2010

Статические члены шаблонных классов должны быть инициализированы в заголовочном файле, и последние компиляторы C ++ и их компоновщики должны обрабатывать это правильно.

Но вы правы, у некоторых очень старых компиляторов есть проблемы с этим.

В этих случаях это обходной путь для инициализации статических элементов ровно один раз в произвольной единице компиляции для каждого типа, для которого используется шаблон синглтона.

В документации gcc есть такие же подробности об этом: http://gcc.gnu.org/onlinedocs/gcc/Template-Instantiation.html.

Я помню один встроенный проект (не так давно), в котором старый компилятор все еще использовался и который молча создавал несколько экземпляров статических членов шаблона. Очевидно, очень плохая идея хранить синглтон в нем ...

Хуже того, единственным синглтоном, используемым в библиотеке (сторонней платформой), был какой-то объект конфигурации, который обычно инициализировался таким же образом, поэтому ошибка возникала только при изменении конфигурации во время выполнения. Потребовалось несколько дней, чтобы отследить ошибку, пока мы наконец не увидели в разборке, что «один и тот же» элемент доступен в разных областях памяти.

1 голос
/ 10 августа 2010

В настоящее время невозможно лениво создать Singleton в многопоточной среде на C ++.

Многие гуру (в том числе Херб Саттер) признали, что текущее состояние стандарта ничего не гарантирует. Есть хаки для различных компиляторов, и boost предоставляет once средство для этой цели, однако это пестрая коллекция инструкций, специфичных для компиляторов ... это не стандартный C ++ (который не поддерживает потоки).

Единственное решение, которое в настоящее время работает (в соответствии со стандартом), - это инициализация Singleton перед запуском нескольких потоков или в части процесса, которая гарантирует, что только один поток будет иметь к нему доступ.

C ++ 0x вводит потоки в стандарт и, в частности, гарантирует, что локальные статические переменные будут создаваться только один раз даже при наличии нескольких потоков (в случае нескольких одновременных вызовов все блокируются до завершения создания). Поэтому следующий метод:

static MyType& Instance() { static Instance MExemplar; return MExemplar; }

работает, и в этом случае вообще не нужен одноэлементный шаблонный класс.

1 голос
/ 10 августа 2010

Правильность кода, связанного с параллелизмом, здесь сложно оценить.Реализация пытается быть немного слишком умной, на мой взгляд.

OTOH, весь код, связанный с параллелизмом, в основном имеет заглушки, которые ничего не делают.Если это используется в многопоточной среде, я думаю, что все должно быть в порядке.

Но я также думаю, что ваше беспокойство обоснованно.Эти внешние определения статических элементов выглядят так, как будто они нарушают правило one-определение .

Лично я думаю, что этот шаблон должен быть переписан, чтобы иметь параллелизм в качестве аргумента политики длясам шаблон и требовать, чтобы производные классы объявляли свои собственные версии pInstance в соответствующем файле .cpp.

Кто-то еще предложил полагаться на специфическое поведение компилятора в отношении инициализации статических локальных переменных.Я не думаю, что это ужасное предложение, но было бы неплохо иметь опцию, когда вы не можете полагаться на то, что компилятор сделает правильные вещи.

1 голос
/ 10 августа 2010
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...