2010-08-09 2 views
3

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

/** 
* @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 включен в несколько файлы. Я уже пробовал это и, похоже, работает, но я не могу понять, почему он работает :).

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

EDIT: Модифицированная реализация с использованием основанного на политике дизайна, как предлагается в принятом решении.

/** 
* 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; 
+3

[Вы делаете ** не ** нуждаетесь в синглетоне.] (Http://jalf.dk/blog/2010/03/singletons-solving-problems-you-didnt-know-you-never-had-since -1995 /) Не используйте синглтон. Вы хотите глобальный, поэтому используйте глобальный. – GManNickG

+1

Я согласен с GMan in, не использую singeltons. Я не согласен, что они такие же, как глобальные (ленивая инициализация). И я ненавижу использование указателей, так как они не удаляются автоматически (используйте статическую функциональную переменную внутри get_instance() (сохраняйте блокировки) таким образом, чтобы одноэлемент был удален правильно). PS. Вам нужно переместить 'flag_ = true;' внутри блокировок, иначе вы могли бы получить несколько потоков, создающих экземпляр. –

+1

@Martin: Ну, вы можете украсть синглтоны решений для творческого творчества, чтобы сделать хорошую глобальную библиотеку утилиты. В основном это дерьмо с ограниченным экземпляром, которое запутывает и ненужно. Я рассматриваю возможность предоставления глобальной библиотеки Boost, так как ее не хватает. – GManNickG

ответ

1

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

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

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

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

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

+0

Мне нравится идея дизайна на основе политики; то у меня может быть политика по умолчанию, которая ничего не делает, а производные классы могут предоставлять свои собственные реализации, которые имеют дело с параллелизмом. Я попробую это сегодня вечером. Благодаря! – Praetorian

1

Не обязательно быть таким сложным.
Simple C++ logger by using singleton pattern

+0

Предложение в конце этой ссылки основывается на специфике компилятора для решения проблемы параллелизма. – Omnifarious

+0

@Omnifarious, ссылка выше просто использует статический одноэлементный объект. Почему этот компилятор специфичен? Не правда ли, что для всех компиляторов все статические объекты создаются до выполнения 'main()'? Таким образом, реализация должна быть потокобезопасной, пока все ваши вызывающие потоки выполняются после запуска 'main()'. – Praetorian

+0

@Praetorian: Нет. Объекты статической функции создаются только при первом использовании (позволяя ленивую оценку). То, что Omnifarious ссылается также, заключается в том, что создание этого статического кода не является потокобезопасным, если вы не используете gcc, который реализовал компилятор определенным образом, чтобы убедиться, что он потокобезопасен. –

3

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

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

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

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

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

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

1

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

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

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

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

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

работа, и в этом случае нет необходимости в одноплодном шаблоне класс на всех.