65.9K
CodeProject 正在变化。 阅读更多。
Home

单例模式:单一实例,但多重实现

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (10投票s)

2012 年 9 月 26 日

CPOL

5分钟阅读

viewsIcon

37668

downloadIcon

322

基于模板的、线程安全的 C++ 单例实现

引言

单例是一种模式,它提供了一种设计策略,有助于控制类的实例创建。通过将构造函数设为 private 并提供一个实例创建函数,它可以确保类只有一个实例(或受控数量的实例)。

单例是一种非常广泛使用的模式。同时,它也可以有多种实现方式。模式规范规定只能创建一个实例或受控数量的实例。如果我们考虑 C++ 版本的单例,实现该模式有多种方法。另一个需要考虑的方面是多线程。我们将考虑不同的设计策略,并研究如何实现一个线程安全的单例对象。

最简单的设计

请参见下面的代码片段来实现单例

class XMLReader
{
public:
  XMLReader& GetInstance()
  {
      static XMLReader XMLReaderObj;
      return XMLReaderObj;
  }
private:
   XMLReader(){}
};

这是 C++ 中最简单的单例实现形式。它创建了一个 static 局部对象并返回其引用。由于 static 对象存在于全局内存中,它将一直存在直到进程结束。在这里,我们无法控制对象的生命周期。它在应用程序 main 函数返回时被销毁。假设我们需要在 main 函数返回之前执行一些清理工作。例如,我们需要将一些信息保存到 XML 文件中。我们可以使用 MSXML 等 XML 库来管理 XML 文件。该库基于 COM。我们为每个线程初始化一次 COM,使用 CoInitializeCoInitializeEx,并在线程返回之前调用 CoUninitialize。请参见下面的 XMLWriter 的完整源代码。

class XMLWriter
{
public:
    static XMLWriter& GetInstance()
    {
        static XMLWriter SingletonObj;
        return SingletonObj;
    }
    bool UpdateXML(const DataHolder& DataHolderObj)
    {
       //XML updating code
    }
    ~XMLWriter()
    {
       _bstr_t bstrFileName(_T("E:\\Test.xml"));
       m_pXMLDom->save( bstrFileName );
    }
private:
    XMLWriter()
    {	
        InitDOM();
    }
    void InitDOM()
    {
        HRESULT hr = m_pXMLDom.CreateInstance(__uuidof(MSXML2::DOMDocument60));
        if (SUCCEEDED(hr))
        {
			m_pXMLDom->loadXML(       
			_bstr_t(_T("<?xml version='1.0'?>\n") 
			_T("<doc title='test'>\n")
			_T("   <page num='1'> \n") 
			_T("      <para title='Saved at last'> \n") 
			_T("          This XML data is finally saved.\n") 
			_T("      </para>\n")
			_T("   </page>\n")
			_T("   <page num='2'>\n") 
		        _T( "      <para>\n")
			_T("          This page is intentionally left blank.\n")
			_T("      </para>\n") 
			_T("   </page>\n")
			_T("</doc>\n")));
        }
    }
private:
    MSXML2::IXMLDOMDocumentPtr m_pXMLDom;
};

void main()
{
    //Initialize COM
    CoInitialize(0);
    XMLWriter& WriterObj = XMLWriter::GetInstance();
    ///some code using ReaderObj
    //...........
    CoUninitialize();
}

当然,这段代码会破坏应用程序,并在退出时崩溃。如果我们查看 XMLWriter 的析构函数,它正试图将加载的 XML 保存到文件中。但在 main 函数返回之前,COM 已经被卸载,而保存代码需要 COM 环境。当 main 函数返回时,析构函数被调用,保存文件的代码执行。砰!!!

我们可以将 CoInitialize 调用放在构造函数中,将 CoUninitialize 放在析构函数中。但在多线程应用程序中,我们无法保证对象是在哪个线程的上下文中创建的,因此 CoInitialize 的调用可能发生在某个工作线程中,而 CoUninitialize 在主线程中(在析构函数内)作为应用程序退出的一部分被调用。实际上,主线程没有调用匹配的 CoInitialize。这可能导致未定义的结果。

这是一个简单的例子,说明我们需要控制销毁过程。当对象销毁时不需要做任何事情时,使用局部静态对象是可以的。事实上,它是一个线程安全的实现。编译器会做必要的工作,以确保在多线程环境中使用的 static 对象是单例的。

现在,我们将研究单例实现中的其他策略。

用于更精细控制的创建者和销毁者方法

请参见下面的代码片段

class XMLReader
{
public:
    static XMLReader& CreateInstance()
    {
        if(!m_pXMLReader)
        {           
           m_pXMLReader = new XMLReader;
        }
        return (*m_pXMLReader);
    }
    static XMLReader& GetInstance()
    {
       return (*m_pXMLReader);
    }
    static void DestroyInstance()
    {
       delete m_pXMLReader;
    }
    ~XMLReader()
    {
    }
private:
   
    XMLReader()
    {
    }

private:
    static XMLReader* m_pXMLReader;
};

这段代码看起来还可以。但如果在 CreateInstance 中发生内存分配失败怎么办?如果 new 返回 NULL,它将返回一个 NULL 引用。如果分配失败作为 std::bad_alloc 返回,CreateInstance 可以抛出异常,单例对象创建代码应该知道这一点。在 new 返回 NULL 的情况下,GetInstance 也返回一个 NULL 引用,这可能导致运行时错误。下面展示了此实现的修改版本。

class XMLReader
{
public:
    static XMLReader& CreateInstance()
    {
        try
        {
            if(!m_pXMLReader)
            {
                m_pXMLReader = new XMLReader;
            }
            return (*m_pXMLReader);
        }
        catch(std::bad_alloc& baException)
        {
            throw new ObjectCreationFailed(_T("Memory allocation failed"));
        }
        catch(...)
        {
            throw new ObjectCreationFailed(_T("Unknown reason"));
        }
    }
    static XMLReader& GetInstance()
    {
        if(!m_pXMLReader)
        {
            throw new ObjectDoesNotExist;
        }
        return (*m_pXMLReader);
    }
    static void DestroyInstance()
    {
        delete m_pXMLReader;
    }
    ~XMLReader()
    {
    }
private:
    XMLReader()
    {
    }
private:
    static XMLReader* m_pXMLReader;
};

多线程方面

在上述版本中,如果对象创建过程中发生任何错误,对象本身将不会被创建。这要好得多。那么,线程安全方面呢?如果两个线程同时调用 CreateInstance 会发生什么?当然,可能会创建多个实例。这违反了单例只有一个实例的假设。我们如何解决这个问题?

解决方案在多线程环境中非常普遍,我们需要使用临界区或互斥锁来同步对象创建。我们将在接下来的部分讨论完整的解决方案。

XMLReader 类的用法如下所示

XMLReader& XMLRdrObj =
XMLReader::CreateInstance();
//use XMLReader
XMLReader::DestroyInstance();

假设我们有多个子线程,并且它们都需要同一个单例类。可能,我们需要从 thread1thread2(例如)调用 CreateInstance()。同样,在线程结束之前调用 DestroyInstance。但是,我们如何确保两个线程始终看到一个有效的实例呢?如果 thread1 先结束,而 thread2 继续运行怎么办?如果 thread1 调用 DestroyInstance,而 thread2 正在使用一个已删除的实例,那将是灾难性的。这个问题表明我们需要一个引用计数机制,这样对象只有在调用最终的 DestroyInstance 时才会被销毁。请参见以下实现。

class XMLReader
{
public:
    static XMLReader& CreateInstance()
    {
        try
        {
            if(!m_pXMLReader)
            {
                m_pXMLReader = new XMLReader;
            }
            ++m_nRefCount;
            return (*m_pXMLReader);
        }
        catch(std::bad_alloc& baException)
        {
            throw new ObjectCreationFailed(_T("Memory allocation failed"));
        }
        catch(...)
        {
            throw new ObjectCreationFailed(_T("Unknown reason"));
        }
    }
    static XMLReader& GetInstance()
    {
        if(!m_pXMLReader)
        {
            throw new ObjectDoesNotExist;
        }
        return (*m_pXMLReader);
    }
    static void DestroyInstance()
    {
        --m_nRefCount;
        if(0 == m_nRefCount)
            delete m_pXMLReader;
    }
    ~XMLReader()
    {
    }
private:
    XMLReader()
    {
    }
private:
    static int m_nRefCount;
    static XMLReader* m_pXMLReader;
};

这似乎好多了。我们还有最后一个待解决的问题:线程安全的实例创建。在这里,我将介绍一个完整的解决方案,它是一个模板类,可以重用于创建单例类。

解决方案

请看下面的代码

/*/////////////////////////////////////////////////////////////////////////////////////
  SingletonTemplate.h - The SingletonTemplate is class which provides a generic template
  for implementing singleton pattern. This file contains the declaration/definition of the
  SingletonTemplate and its supporting classes.
 
  @written by :  Sudheesh.P.S
  @version    :  1.0            
  @date       :  26-09-2012
  @info       :  Initial version
//////////////////////////////////////////////////////////////////////////////////////*/
#pragma once

#include <exception>

/*/////////////////////////////////////////////////////////////////////////////////////
  CriticalSectionObject - A wrapper class for CRITICAL_SECTION. It manages the 
  initialization, locking, unlocking and deletion of CRITICAL_SECTION object. It is
  used by AutoCriticalSection class for automatic management of critical code areas.
//////////////////////////////////////////////////////////////////////////////////////*/
class CriticalSectionObject
{
public:
    CriticalSectionObject()
    {
        InitializeCriticalSection(&m_stCriticalSection);
    }
    void Lock()
    {
         EnterCriticalSection(&m_stCriticalSection);
    }
    void UnLock()
    {
         LeaveCriticalSection(&m_stCriticalSection);
    }
    ~CriticalSectionObject()
    {
        DeleteCriticalSection(&m_stCriticalSection);
    }
private:
    CRITICAL_SECTION m_stCriticalSection;
};

/*/////////////////////////////////////////////////////////////////////////////////////
  AutoCriticalSection - An automatic which uses the CriticalSectionSyncObj class to
  automatically lock the critical code area, by calling Lock in constructor and
  helps to leave the critical code by calling UnLock in destructor. Since it is done
  in constructor/destructor, it automatically happens.
/////////////////////////////////////////////////////////////////////////////////////*/

class AutoCriticalSection
{
public:
    AutoCriticalSection(CriticalSectionObject* pCSObj)
    {
        m_pCSObj = pCSObj;
        if( m_pCSObj  )
        {
            m_pCSObj->Lock();
        }
    }

    ~AutoCriticalSection()
    {
        if( m_pCSObj  )
        {
            m_pCSObj->UnLock();
        }
    }
private:
    CriticalSectionObject* m_pCSObj;
};

extern CriticalSectionObject g_CSObj;

/*/////////////////////////////////////////////////////////////////////////////////////
  GeneralException - A base class for exceptions
/////////////////////////////////////////////////////////////////////////////////////*/

class GeneralException
{
public:
	GeneralException(LPCTSTR lpctszDesciption)
	{
           m_ptcDescription = StrDup(lpctszDesciption);
	}
	~GeneralException()
	{
	   LocalFree(m_ptcDescription);
	}
	TCHAR* GetDescription()
	{
	   return m_ptcDescription;
	}
private:
	TCHAR* m_ptcDescription;
};

/*/////////////////////////////////////////////////////////////////////////////////////
  ObjectCreationFailedException - It is thrown when the object creation fails. This
  can be due to memory allocation failure or any other unknown cases.
/////////////////////////////////////////////////////////////////////////////////////*/

class ObjectCreationFailedException:public GeneralException
{
public:
    ObjectCreationFailedException(LPCTSTR lpctszDesciption):GeneralException(lpctszDesciption)
    {
    }
};

/*/////////////////////////////////////////////////////////////////////////////////////
  ObjectCreationFailedException - Thrown when a NULL object exist. This is a precaution
  when GetInstance is called on a destroyed instance (after DestroyInstance).
/////////////////////////////////////////////////////////////////////////////////////*/

class ObjectDoesNotExistException:public GeneralException
{
public:
    ObjectDoesNotExistException():GeneralException(_T("Object does not exist"))
    {
    }
};

/*///////////////////////////////////////////////////////////////////////////////////////
  SingletonTemplate - Singleton is a pattern which provides a design strategy which
  helps to control the instance creation of a class. It helps to maintain only a single
  instance of a class by making the constructor private and providing a CreateInstance
  function to create the class instance. The SingletonTemplate class provides a template
  class which can simplify the implementation of singleton classes. It synchronizes the
  instance creation with a critical section and also counts the reference for each 
  CreateInstance and decrements the reference for each DestroyInstance. 
  The requirements for using this class are:
  (1) - Make the constructors of target singleton class private
  (2) - Add the SingletonTemplate as friend of the target class (please sample class)
///////////////////////////////////////////////////////////////////////////////////////*/

template <class T> class SingletonTemplate 
{
public:
    /*////////////////////////////////////////////////////////////////////////////////
      Creates the instance of the singleton class. It is thread safe and keeps
      the reference count.
    ////////////////////////////////////////////////////////////////////////////////*/
    static T& CreateInstance()
    {
        try
        {
            AutoCriticalSection ACSObj(&g_CSObj);
            if( 0 == m_pTSingleInstance )
            {
                m_pTSingleInstance = new T;
            }
            ++m_nRefCount;
        }
        catch(std::bad_alloc)
        {
            throw new ObjectCreationFailedException(_T("Memory allocation failed"));
        }
        catch(...)
        {
            throw new ObjectCreationFailedException(_T("Unknown reason"));
        }
        return *m_pTSingleInstance;
    }

    /*///////////////////////////////////////////////////////////////////////////////////
      Returns the instance of the singleton class. If not exists, creates a new instance
    ///////////////////////////////////////////////////////////////////////////////////*/
    static T& GetInstance()
    {
        if( 0 == m_pTSingleInstance )
        {            
            throw new ObjectDoesNotExistException;
        }
        return *m_pTSingleInstance;
    }

    /*//////////////////////////////////////////////////////////////////////////////////
      Destroys the instance of the singleton class. It is thread safe and decrements
      the reference count. If the reference count is zero deletes the instance.
    /////////////////////////////////////////////////////////////////////////////////*/
    static void DestroyInstance()
    {
        AutoCriticalSection ACSObj(&g_CSObj);
        (m_nRefCount > 0) ? --m_nRefCount:m_nRefCount;
        if( 0 == m_nRefCount )
        {
            delete m_pTSingleInstance;
            m_pTSingleInstance = 0;
        }
    }
private:
    static T* m_pTSingleInstance;
    static int m_nRefCount;
};

template <class T> T* SingletonTemplate<T>::m_pTSingleInstance = 0;
template <class T> int SingletonTemplate<T>::m_nRefCount = 0;

以下代码展示了如何使用 SingletonTemplate

#pragma once

#include "SingletonTemplate.h"

class SequenceGenSingleton 
{
    friend class SingletonTemplate<SequenceGenSingleton>;
public:
    ~SequenceGenSingleton(void);
    long GetNextSeqNum();
private:
    SequenceGenSingleton(void);
    SequenceGenSingleton(int nInitVal);
private:
    long m_lSeqNum;    
};

以下代码展示了此类用法

SingletonTemplate<SequenceGenSingleton>::CreateInstance();
SequenceGenSingleton& SingleObj = SingletonTemplate<SequenceGenSingleton>::GetInstance();
SingleObj.GetNextSeqNum();
SingletonTemplate<SequenceGenSingleton>::DestroyInstance();

SingletonTemplate 应该被设为目标单例类的友元(这使得它能够访问 private 构造函数)。

DLL 和单例

在使用 DLL 的情况下,代码可以如下所示

BOOL WINAPI DllMain( IN HINSTANCE hDllHandle, 
         IN DWORD     dwReason, 
         IN LPVOID    lpvReserved )
 {
   switch ( dwReason )
   {
    case DLL_PROCESS_ATTACH:
	 SingletonTemplate<SequenceGenSingleton>::CreateInstance();
      break;

    case DLL_PROCESS_DETACH:
         SingletonTemplate<SequenceGenSingleton>::DestroyInstance();
      break;
   }
}

从线程使用

在使用子线程的情况下,代码可以如下所示

DWORD WINAPI MyWorkerThread(LPVOID)
{
    SingletonTemplate<SequenceGenSingleton>::CreateInstance();
    //…………..
    //The thread code
    //…………..
    SingletonTemplate<SequenceGenSingleton>::DestroyInstance();
    return 0;
}

线程函数可能有多个返回路径。因此,我们需要在所有返回路径中调用 DestroyInstance。这使得定义一个自动类来自动化释放过程成为可能。它可以如下所示。

/*///////////////////////////////////////////////////////////////////////////////////////
  AutoSingleton - Makes the calls to CreateInstance and DestroyInstance automatic.
  This makes the usage of SingletonTemplate easy in the case of multiple return paths.
///////////////////////////////////////////////////////////////////////////////////////*/

template <class T> class AutoSingleton
{
public:
	AutoSingleton()
	{
    	   SingletonTemplate<T>::CreateInstance();
	}
	~AutoSingleton()
	{
	   SingletonTemplate<T>::DestroyInstance();
	}
};

AutoSingleton<SequenceGenSingleton> AutoSingletonObj;
SequenceGenSingleton& SingleObj = SingletonTemplate<SequenceGenSingleton>::GetInstance();

总结

这就是我目前所能提供的。我希望提供的模板类会很有用,因为它消除了线程同步和引用计数的需要。通过用所需的类参数化 SingletonTemplate 并将其设为类的友元,我们可以创建一个线程安全的单例类。

历史

  • 2012 年 9 月 26 日:初始版本
© . All rights reserved.