C++之竹

无论是太阳下,还是风雨中,都要成长!

常用链接

统计

最新评论

Singleton模式——C++应用(三)

前面对C++的Singleton模式的探讨还都是针对通过静态变量来创建对象。但学习嘛,多走点总不是坏事。

接下来就来看看通过 new 来创建单件对象的单件类设计。既然是用 new 来创建了,那自然就不能忽略需要用 delete 来释放。

好了,先来看看代码:

  1// Singleton demo_2: Singleton instance is created by new.
  2// [delete instance manually]
  3
  4#include <Windows.h>
  5#include <iostream>
  6
  7class A 
  8{
  9private:
 10    static A* ms_pInstance;
 11
 12public:
 13    static A& GetInstance();
 14    static void Release();
 15
 16private:
 17    A() : m_nStat1(-1), m_nStat2(-1{
 18        m_nStat1 = 0;
 19        std::cout << "Construct A" << std::endl;
 20        m_nStat2 = 0;
 21    }

 22    A(const A&);
 23
 24public:
 25    ~A() {
 26        m_nStat1 = 0;
 27        std::cout << "Destruct A" << std::endl;
 28        m_nStat2 = 0;
 29    }

 30
 31    void Do() {
 32        ++m_nStat1;
 33        ++m_nStat2;
 34        std::cout << "Called Do() by object of A. [" 
 35            << m_nStat1 << "" 
 36            << m_nStat2 << "]" 
 37            << std::endl;
 38    }

 39
 40private:
 41    int m_nStat1;
 42    int m_nStat2;
 43}
;
 44
 45class C
 46{
 47
 48public:
 49    static C& GetInstance();
 50    static void Release();
 51
 52private:
 53    C() : m_nStat(-1{
 54        std::cout << "Construct C" << std::endl;
 55        m_nStat = 0;
 56    }

 57    C(const C&);
 58
 59public:
 60    ~C() {
 61        std::cout << "Destruct C" << std::endl;
 62        m_nStat = 0;
 63    }

 64
 65    void Do() {
 66        ++m_nStat;
 67        std::cout << "Called Do() by object of C. [" 
 68            << m_nStat << "]" 
 69            << std::endl;
 70    }

 71
 72private:
 73    int m_nStat;
 74}
;
 75
 76class B
 77{
 78public:
 79    B(int nID) : m_nID(nID) {
 80        std::cout << "Construct B: " << m_nID << std::endl;
 81        A::GetInstance().Do();
 82        C::GetInstance().Do();
 83    }

 84    ~B() {
 85        std::cout << "Destruct B: " << m_nID << std::endl;
 86        A::GetInstance().Do();
 87        C::GetInstance().Do();
 88    }

 89
 90private:
 91    int m_nID;
 92}
;
 93
 94//CRITICAL_SECTION g_cs; // used for supporting multithreading to singleton.
 95
 96static B gs_B0(0);
 97B g_B1(1);
 98
 99A* A::ms_pInstance = NULL;
100A& A::GetInstance()
101{
102    if (NULL == ms_pInstance)
103    {
104        //EnterCriticalSection(&g_cs);
105        //if (NULL == ms_pInstance)
106        //{
107            ms_pInstance = new A;
108        //}
109        //LeaveCriticalSection(&g_cs);
110    }

111
112    return *ms_pInstance;
113}

114
115void A::Release()
116{
117    std::cout << "A::Release()" << std::endl;
118    if (ms_pInstance != NULL)
119    {
120        delete ms_pInstance;
121        ms_pInstance = NULL;
122    }

123}

124
125C& C::GetInstance()
126{
127    static C* s_pInstance = new C;
128
129    return *s_pInstance;
130}

131
132void C::Release()
133{
134    std::cout << "C::Release()" << std::endl;
135    delete &GetInstance();
136}

137
138static B gs_B2(2);
139B g_B3(3);
140
141int main(int argc, char * argv[])
142{
143    //InitializeCriticalSection(&g_cs);
144
145    std::cout << "Enter main" << std::endl;
146    A::GetInstance().Do();
147    C::GetInstance().Do();
148
149    A::Release();
150    C::Release();
151
152    //DeleteCriticalSection(&g_cs);
153    system("pause");
154    return 0;
155}

156

[单件类的线程安全性]

代码中,类A和C都是单件类,且都是通过 new 来分配和创建单件对象,在对象的释放上,也统一的使用Release函数封装了delete操作,并手动调用。但A的单件和C的单件又是不同的,这个不同就在于 A 是通过将 new 出来的对象指针赋给成员变量,而 C 则将 new 出来的对象指针给了局部静态变量。可别小看了这个区别哦!!就因为这一不同,结果 A 不是线程安全的,而 C 却是线程安全的。当然,也可以通过使用临界区、互斥量等来是 A 变为线程安全,可是,该在什么时候去初始化临界区或创建互斥量对象等呢?以临界区为例,按常规的在 main 的头部进行临界区的初始化,末尾则进行临界区的删除;但是很不幸,程序崩溃了!!因为全局变量在构造是调用了单件A,而单件A创建用到了临界区,可这是临界区却还没有初始化。
那么将这临界区的初始化放到A的构造呢?一样使用先去初始化。
放到A::GetInstance()内呢?影响效率,且存在重复初始化。

放到B的构造呢?还是被反复多次初始化了……。而且,有非B类的更先构造并调用了单件A的全局对象呢?!

——看来,从线程安全考虑,单件类A的设计应当被否决了。

 [单件类对象的释放]

 撇开线程安全问题不看,就上面的Demo,我们将会发现另一个严重问题——何时才能调用A和C的Release方法,释放A和C的单件对象呢?如果,是如Demo中那样,在main函数末尾进行释放,那么因为类B的几个全局对象的析构,将发生如下的糟糕结果:

  • 单件对象A又被再次重新分配,但却未能得到再次重新分配,从而造成内存泄露。事实上,对象的内部数据也已不再是我们所需要的数据了。
  • 单件对象C,则因为是static而不会再次分配,所以在B的全局对象析构中,将会使用已经析构了的单件C。

但是,如果我们把Release放入B的析构中,则A将被多次的分配和释放,而C则被多次调用析构。无奈咯,考虑释放的困难,这里的A和C的单件类设计方式看来也都还是否决的好!~

 

posted on 2012-03-13 00:55 青碧竹 阅读(240) 评论(0)  编辑 收藏 引用 所属分类: 设计模式


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理