【问题标题】:C++ singleton template classC++ 单例模板类
【发布时间】:2010-08-09 21:43:06
【问题描述】:

在最近的一个项目中,我必须创建一个 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;

【问题讨论】:

  • You do not need a singleton. 不要使用单例。你想要一个全局的,所以使用一个全局的。
  • 我同意 GMan 不要使用单数。我不同意它们与全局变量(延迟初始化)相同。而且我讨厌使用指针作为表示,因为它们不会自动删除(在 get_instance() 中使用静态函数变量(保持锁),这样单例将被正确删除)。 PS。您需要在锁内移动flag_ = true;,否则您可能会获得多个线程来创建实例。
  • @Martin:你可以窃取单身人士使用的创意创作解决方案来制作一个不错的全局实用程序库。主要是限制实例的废话令人困惑且不必要。我正在考虑向 Boost 提交一个全局库,因为它缺少一个。
  • @Martin:flag_ = true; 在互斥锁之外没关系;只有一个线程可以通过(*SingletonDynamic&lt; T &gt;::pfnLockMutex)();,并且该线程将修改pInstance_ 变量,使其不为NULL。这将防止在锁处等待的线程创建新实例。
  • 你是对的。哎呀。

标签: c++ design-patterns singleton


【解决方案1】:

模板类的静态成员必须在头文件中初始化,最近的 C++ 编译器及其链接器必须正确处理。

但是你是对的,一些非常老的编译器有这个问题。

在这些情况下,对于使用单例模板的每种类型,在任意编译单元中只初始化一次静态成员是一种解决方法。

gcc 文档对此有相同的详细信息:http://gcc.gnu.org/onlinedocs/gcc/Template-Instantiation.html

我记得一个嵌入式项目(不久前),其中一个旧编译器仍在使用中,并且那个编译器默默地创建了模板静态成员的多个实例。 显然,在其中存储 Singleton 是一个非常糟糕的主意....

更糟糕的是,库(第三方框架)中唯一使用的 Singleton 是一些通常以相同方式初始化的配置对象,因此该错误仅在运行时更改配置时发生。 花了好几天的时间来追踪这个错误,直到我们终于在反汇编中看到“相同”的成员在不同的内存区域被访问。

【讨论】:

    【解决方案2】:

    这里的并发相关代码的正确性很难评估。在我看来,这个实现有点太聪明了。

    OTOH,所有与并发相关的代码基本上都有其背后的存根,它们什么都不做。如果这个用在非线程环境下,我觉得应该没问题。

    但是,我也认为你的担心是有根据的。静态成员的那些外部定义似乎违反了one definition rule

    就我个人而言,我认为应该重写此模板,以将并发内容作为模板本身的策略参数,并要求派生类在适当的 .cpp 文件中声明它们自己的 pInstance 版本。

    其他人建议依赖编译器特定的行为来初始化静态局部变量。我不认为这是一个糟糕的建议,但是当你不能依赖编译器做正确的事情时,有一个选项可能会很好。

    【讨论】:

    • 我喜欢基于策略的设计理念;然后我可以拥有什么都不做的默认策略,并且派生类可以提供自己的处理并发的实现。我今晚试试。谢谢!
    【解决方案3】:

    【讨论】:

    • 该链接末尾的建议依赖于编译器特定的行为来处理并发问题。
    • @Omnifarious,上面的链接只使用了一个静态单例对象。为什么那个编译器是特定的?不是所有编译器都在main() 执行之前构造所有静态对象吗?因此,只要在 main() 开始执行之后执行所有调用线程,该实现就应该是线程安全的。
    • @Praetorian:不,静态函数对象仅在首次使用时创建(允许延迟评估)。 Omnifarious 也指的是,这个静态的创建不是线程安全的,除非您使用 gcc,它以特定方式实现了编译器以确保它是线程安全的。
    • @Martin:我不知道函数中的静态对象。但我之前所说的确实适用于在文件范围内声明的静态对象,不是吗?
    • @Praetorian - 是的,确实如此。但是链接到的示例在文件范围内没有使用静态。
    【解决方案4】:

    目前无法在 C++ 的多线程环境中懒惰地创建 Singleton。

    许多专家(其中包括 Herb Sutter)都承认该标准的当前状态并不能保证任何事情。各种编译器都有 hack,boost 提供了once 工具来实现这个目的,但是它是编译器特定指令的杂乱无章的集合……它不是标准的 C++(线程不知道)。

    目前唯一可行的解​​决方案(根据标准)是在启动多个线程之前或在保证只有一个线程将访问它的进程的一部分中初始化 Singleton。

    C++0x 将线程引入了标准,并且特别保证了局部静态变量即使在存在多个线程的情况下也只会被创建一次(在多个同时调用的情况下,所有的都会阻塞直到创建结束)。因此采用以下方法:

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

    有效,在这种情况下根本不需要单例模板类。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多