C ++ singleton pattern of several implementations parse

Singleton pattern implemented in two modes:

1) lazy mode: that is to create a unique instance of an object only when you first use, in order to achieve the effect of delayed loading.

2) starving mode: that whatever you do in the future, will create a unique instance of an object when the program starts.

So, the way to achieve from the point of view, lazy mode initialization is done only when the first use of singleton object. Because at this time there may be multi-threaded race environment, such as an unlocked restrictions would cause duplicate construction or construction is not complete.

Starving mode is the use of external variables, initialization is completed singleton object prior to entering the program entry function, so there is no case is the thread race multithreaded environment, and therefore without locking.

The following are typical of several implementations

A, lazy mode, the standard "double check lock" + "automatic recovery" Implementing

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Singleton
{
public :
     static Singleton* GetInstance()
     {
         if (m_pInstance == NULL )
         {
             Lock(); // 加锁
             if (m_pInstance == NULL )
             {
                 m_pInstance = new Singleton ();
             }
             UnLock(); // 解锁
         }
         return m_pInstance;
     }
 
     // 实现一个内嵌垃圾回收类   
     class CGarbo
     {
     public :
         ~CGarbo()
         {
             if (Singleton::m_pInstance)
                 delete Singleton::m_pInstance;
         }
     };
 
     static CGarbo Garbo; // 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
 
private :
     Singleton(){};
     Singleton(Singleton const &);
     Singleton& operator=(Singleton const &);
 
     static Singleton* m_pInstance;
};
 
Singleton* Singleton::m_pInstance = NULL;
Singleton::CGarbo Garbo;

Second, the static local variable lazy mode, rather than new objects created on the heap, to avoid their own recycling resources.

Note that there is still a local variable initialization thread safety issues, after the C ++ 0X, ask the compiler to ensure the security thread static variable initialization, you can not be locked. But C ++ 0X before, still need to lock.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Singleton
{
public :
     static Singleton* GetInstance()
     {
         Lock(); // not needed after C++0x
         static Singleton instance; 
         UnLock(); // not needed after C++0x
 
         return &instance;
     }
 
private :
     Singleton() {};
     Singleton( const Singleton &);
     Singleton & operator = ( const Singleton &);
};

In lazy mode, if a large number of concurrent threads to obtain singleton object, making frequent lock unlock operation, it will inevitably lead to inefficiencies.

Third, the starving mode, the basic version

Since the beginning of the program to complete the initialization of singleton object, so no need to consider the follow-up multi-threaded security issues can be avoided in lazy mode lock unlock the overhead of frequently.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Singleton
{
public :
 
     static Singleton* GetInstance()
     {
         return &m_instance;
     }
 
private :
     Singleton(){};
     Singleton(Singleton const &);
     Singleton& operator=(Singleton const &);
 
     static Singleton m_instance;
};
 
Singleton Singleton::m_instance;  // 在程序入口之前就完成单例对象的初始化

Although this implementation works well in a certain extent, but in some cases, cause problems --- in C ++ is "non-local static object" of "initialization" order "uncertainty", see Effective c ++ Terms 47.

考虑: 如果有两个这样的单例类,将分别生成单例对象A, 单例对象B. 它们分别定义在不同的编译单元(cpp中), 而A的初始化依赖于B 【 即A的构造函数中要调用B::GetInstance() ,而此时B::m_instance 可能还未初始化,显然调用结果就是非法的 】, 所以说只有B在A之前完成初始化程序才能正确运行,而这种跨编译单元的初始化顺序编译器是无法保证的。

四、饿汉模式,增强版本(boost实现)

在前面的方案中:饿汉模式中,使用到了类静态成员变量,但是遇到了初始化顺序的问题; 懒汉模式中,使用到了静态局部变量,但是存在着线程安全等问题。

boost 的实现方式是:单例对象作为静态局部变量,然后增加一个辅助类,并声明一个该辅助类的类静态成员变量,在该辅助类的构造函数中,初始化单例对象。以下为代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Singleton
{
public :
     static Singleton* GetInstance()
     {
         static Singleton instance;
         return &instance;
     }
 
protected :
     // 辅助代理类
     struct Object_Creator
     {
         Object_Creator()
         {
             Singleton::GetInstance();
         }
     };
     static Object_Creator _object_creator;
 
     Singleton() {}
     ~Singleton() {}
};
 
Singleton::Object_Creator Singleton::_object_creator;

首先,代理类这个外部变量初始化时,在其构造函数内部调用Singleton::GetInstance();从而间接完成单例对象的初始化,这就通过该代理类实现了饿汉模式的特性。

其次,仍然考虑第三种模式的缺陷。 当A的初始化依赖于B,【 即A的构造函数中要调用B::GetInstance() ,而此时B::m_instance 可能还未初始化,显然调用结果就是非法的 】 现在就变为【在A的构造函数中要调用B::GetInstance() ,如果B尚未初始化,就会引发B的初始化】,所以在不同编译单元内全局变量的初始化顺序不定的问题就随之解决。

最后,关于使用懒汉还是饿汉模式,我的理解:

如果这个单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,也是一种资源浪费吧。 所以这种情况懒汉模式(延迟加载)更好。

如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

Guess you like

Origin www.cnblogs.com/wjry/p/11222300.html