C++ 单例模式

 

 


1. Martin Reddy《API Design for C++》-3.1 的讲解:

 

C++ 单例模式_第1张图片

The Singleton design pattern (Gamma et al., 1994) is used to ensure that a class only ever has one instance. The pattern also provides a global point of access to that single instance. You can think of a singleton as a more elegant global variable. However, it offers several advantages over the use of global variables because it:

1. Provides control over the allocation and destruction of the object. Enforces that only one instance of the class can be created.
2. Allows support for thread-safe access to the object’s global state.
3. Avoids polluting the global namespace.


单例模式的常用场景:

The Singleton pattern is useful for modeling resources that are inherently singular in nature.
For example, a class to access the system clock, the global clipboard, or the keyboard.      It’s also useful for creating manager classes that provide a single point of access to multiple resources, such as a thread manager or an event manager. (见到过这种***manager类)。


1.1 Implementing Singletons in C++


The Singleton pattern involves creating a class with a static method that returns the same instance of the class every time it is called. This static method is often called GetInstance(), or similar. There are several C++ language features to consider when designing a singleton class.
• You don’t want clients to be able to create new instances. This can be done by declaring the default constructor to be private, thus preventing the compiler from automatically creating it as public.

You want the singleton to be non-copyable, to enforce that a second instance cannot be created. This can be done by declaring a private copy constructor and a private assignment operator. -- 非常重要!不要允许复制!

• You want to prevent clients from being able to delete the singleton instance. This can be done by declaring the destructor to be private. (Note, however, that some compilers, such as Borland 5.5 and Visual Studio 6, produce an error incorrectly if you try to declare a destructor as private.)

• The GetInstance() method could return either a pointer or a reference to the singleton class. However, if you return a pointer, clients could potentially delete the object. You should therefore prefer returning a reference.


The general form of a singleton in C++ can therefore be given as follows (Alexandrescu, 2001):
 

class Singleton
{
public:
	static Singleton &GetInstance();
private:
	Singleton();
	~Singleton();
	Singleton(const Singleton &);
	const Singleton &operator=(const Singleton &);
};

-----好多private,哈哈;  有的实现中,会加一个private  static Singleton*指针做成员变量, 效果一样,, 本书中更倾向于用引用。

Then user code can request a reference to the singleton instance as follows:
Singleton &obj = Singleton::GetInstance();

Note:

Declaring the constructor and destructor to be private also means that clients cannot create subclasses of the singleton. However, if you wish to allow this, you can of course simply declare them to be protected instead.
 

注意点:(需要继续理解)

C++ 单例模式_第2张图片

 

1.2 Making Singletons Thread Safe


The implementation of GetInstance() presented earlier is not thread safe because there is a race condition in the initialization of the Singleton static.   If two threads happen to call this method at the same time, then the instance could be constructed twice or it could be used by one thread before it has been fully initialized by the other thread. This race condition is more evident if you look at the
code that the compiler will generate for this method. Here’s an example of what the GetInstance() method might get expanded to by a compiler:  (atexit函数的意义是什么?
 

Singleton &Singleton::GetInstance()
{
	// Example code that a compiler might generate. . .
	extern void __DestructSingleton();
	static char __buffer[sizeof(Singleton)];
	static bool __initialized = false;
	
	if (! __initialized)
	{
		new(__buffer) Singleton(); // placement new syntax
		atexit(__DestructSingleton); // destroy instance on exit
		__initialized = true;
	}
	return *reinterpret_cast(__buffer);
}
void __DestructSingleton()
{
	// call destructor for static __buffer Singleton object
}

 

Thread safe的方案:

1) 暴力加锁  -- 虽然安全,但严重影响软件效率

Singleton &Singleton::GetInstance()
{
	mutex.lock();
	static Singleton instance;
	mutex.unlock();
	return instance;
}

 

2) Double Check Locking Pattern (DCLP)  -- 有书上称这种方式是臭名昭著的一种方法!
 

面试时问到过这种双重锁如何写的细节,注意细节! 两次check,check的是是否已经实例化过了!

Singleton &Singleton::GetInstance()
{
	static Singleton *instance = NULL;
	
	if(!instance) // check #1
	{
		mutex.lock();

		if(!instance) // check #2
		{
			instance = new Singleton();
		}
		mutex.unlock();
	}
	return *instance;
}

Note:(涉及到c++ 内存模型等:)

However, the DCLP is not guaranteed to work on all compilers and under all processor memory models. (所以要慎用这种方法!)

For example, a shared-memory symmetric multiprocessor normally commits writes to memory in bursts, which may cause the writes for different threads to be reordered.   Using the volatile keyword is often seen as a solution to this problem because it synchronizes read and write operations to the volatile data.   However, even this approach can be flawed in a multithreaded environment (Meyers and Alexandrescu, 2004). You may be able to use platform-specific memory barriers to get around these problems or, if you’re only using POSIX threads, you could use pthread_once()。

  Ref:   Linux下pthread_once()函数: https://blog.csdn.net/ericbar/article/details/79879850

           c++ 内存模型: https://blog.csdn.net/qq_35865125/article/details/105611985

 

3)饿汉模式(在用户使用之前就主动初始化,避免了多线程环境下的初始化问题)

方法1:

C++ 单例模式_第3张图片

I prefer:

class CSingleton
{
private:
    CSingleton()
    {
    }
    static CSingleton *p;
public:
    static CSingleton& getInstance()
    {
        return *p;
    }
};
CSingleton* CSingleton::p = new CSingleton();

方法2:

C++ 单例模式_第4张图片


 

2. xx《xx》-x.x 的讲解:

 

 


3. 从前的整理

 

单例模式就是一个类只能被实例化一次 ,即,只能有一个对象的类。

单例模式,可以说设计模式中最常应用的一种模式了,据说也是面试官最喜欢的题目。

一般情况下,我们建立的一些类是属于工具性质的,(例如,模型的数学算法类,假设有1000个车辆对象,每个车辆对象中存储着赛车的基本参数,这些对象只用一个算法就可以),基本不用存储太多的跟自身有关的数据,在这种情况下,每次都去new一个对象,即增加了开销,也使得代码更加臃肿。其实,我们只需要一个实例对象就可以。

 

实现:

对于一个普通的类A,我们可以通过 A a; A* p = new A; 这种方式来生成一个对象,显然,如果要实现单例模式类的话,这种方式是不能被允许的,否则,可以生成多个类对象哦。

如何禁止用上面的这两种方式实例化一个类呢?

方法是把构造函数私有化,上面这两种方法都会默认的去调用构造函数,当构造函数是private或者protected时,构造函数将无法从外部调用。  我们将默认的构造函数声明为私有的,这样就不会被外部所new了。

(甚至可以将析构函数也声明为私有的,这样就只有自己能够删除自己了??)

既然构造函数是私有了,那么他就只能被类内部的成员函数调用,所以我们可以搞一个public函数去供外部调用,然后这个函数返回一个对象,为了保证多次调用这个函数返回的是一个对象,我们可以把类内部要返回的对象设置为静态的。

具体实现如下:

注意: 1)要加锁,防止多个线程同时调用getInstance函数,导致生成多个对象。2)静态成员变量要在类外初始化

lock要放在判断指针是否为NULL的前面。

class CSingleton
{

private:
    CSingleton()//私有类型的构造函数
    {
        pthread_mutex_init(&mtx,0);
    }
    static CSingleton *p;//指针

public:
    static pthread_mutex_t mtx; //互斥锁
    static CSingleton* getInstance()//供外部进行调用,获得唯一的对象
    {
        pthread_mutex_lock(&mtx);
        if(p == NULL)
        {            
            p = new CSingleton(); //如果第一次调用,则生成对象
            
        }
        pthread_mutex_unlock(&mtx);
        return p;
    }
};
pthread_mutex_t CSingleton::mtx; //static类型的成员变量必须进行初始化,且初始化必须在类定义外进行初始化
CSingleton* CSingleton::p = NULL;//private类型的静态成员变量也是可以这样直接初始化的。

上面的方法是在类中定义了一个静态的指针,另一种方法:

把静态对象的放到函数里面,代替静态的指针,这样就省的去类外部初始化静态成员了,只要返回一个静态类对象的地址,就算这个函数执行完也不会被销毁,它被保存在静态区和全局变量差不多。代码:

class CSingleton
{
private:
    CSingleton()
    {
        pthread_mutex_init(&mtx,0);
    }
public:
    static pthread_mutex_t mtx;
    static CSingleton* getInstance()
    {
        //lalaB
        pthread_mutex_lock(&mtx);
        static CSingleton obj;   //静态局部变量
        pthread_mutex_unlock(&mtx);
        return &obj;
    }
};
pthread_mutex_t CSingleton::mtx;

::上面这种会不会有问题,例如 ,getInstance被两个线程几乎同时调用,同时进入到函数内的lalaB位置,然后,其中一个线程先得到lock,然后另外一个线程得到lock,那么这种情况下obj会不会被初始化两次呢?  之所以有这个因为是因为“函数内的static变量,只在函数被第一次调用时才被初始化。” --- 看第一部分,书上给出的例子,应该是安全的!


上面这种方式也被人们成为懒汉模式,为什么叫懒汉?因为这样的方式只有在我调用 CSingleton::getInstance(); 的时候才会返回一个实例化的对象,懒死了,我不要你你就不给我。

下面这种方式就和上面的不同,人家还没要,我就忍不住先给人家准备好了,如饥似渴,所以也叫饿汉模式。

上面第一种方式,类中的静态指针变量要必须在类外部被初始化,否则编译器不会为它分配空间,像这样 CSingleton* CSingleton::p = NULL;             其实我们可以在这一步就new一个对象出来因为p是CSingleton的成员,它是可以调用构造函数的哦,于是我们改成这样就是饿汉模式了。

饿汉模式代码

class CSingleton
{
private:
    CSingleton()
    {
    }
    static CSingleton *p;
public:
    static CSingleton* getInstance()
    {
        return p;
    }
};
CSingleton* CSingleton::p = new CSingleton();

这样锁也不用加了,因为我们调用 CSingleton::getInstance() 之前这个类就已经被实例化了。


总结:

利用静态变量和私有化构造函数的特性来实现单例模式。搞一个静态的自身类指针,然后把构造函数私有化,这样new的时候就只能让本类中的成员调用,然后不择手段在类内部new出这个对象,并提供一种方法供外部得到这个对象的地址。


关于单例类的析构函数的讨论:

(是不是使用shared_ptr可以避免下面这种方式?)  单例在程序结束的时候不是会自动被销毁吗?

例如,饿汉模式中,这个单例类的实例可以是在初始化的时候new出来的, new和delete要成对,所以需要显示地调用delete, 下面给出了一种实施方案。   在方案中作者提到了“程序结束时,系统也会析构所有的类的静态成员变量”,这意味着如果在实施单例模式时,使用的是在类的getInstance函数内部定义一个静态成员实例static Singleton instance 这种方法的话,就不用额外地想办法取delete了!

 

### 方案:###
如果在类的析构行为中有必须的操作,比如关闭文件,释放外部资源,那么必须保证析构函数被合理地执行。

可以在程序结束时调用GetInstance(),并对返回的指针掉用delete操作。这样做可以实现功能,但不仅很丑陋,而且容易出错。因为这 样的附加代码很容易被忘记,而且也很难保证在delete之后,没有代码再调用GetInstance函数。

一个妥善的方法是让这个类自己知道在合适的时候把自己删除,或者说把删除自己的操作挂在操作系统中的某个合适的点上,使其在恰当的时候被自动执行。

我们知道,程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利 用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如下面的代码中的CGarbo类 (Garbo意为垃圾工人):

class CSingleton{

//其他成员

public:

static CSingleton* GetInstance();

private:

    CSingleton(){};

    static CSingleton * m_pInstance;

    class CGarbo //它的唯一工作就是在析构函数中删除CSingleton的实例

    {

        public:

            ~CGarbo() {

                if( CSingleton::m_pInstance )

                    delete CSingleton::m_pInstance;
            }

     }

     Static CGabor Garbo; //定义一个静态成员,程序结束时,系统会自动调用它的析构函数
};

类CGarbo被定义为CSingleton的私有内嵌类,以防该类被在其他地方滥用。

程序运行结束时,系统会调用CSingleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。

使用这种方法释放单例对象有以下特征:

在单例类内部定义专有的嵌套类;

在单例类内定义私有的专门用于释放的静态成员;

利用程序在结束时析构全局变量的特性,选择最终的释放时机;

使用单例的代码不需要任何操作,不必关心对象的释放。


Ref:

https://www.cnblogs.com/dupengcheng/p/7205527.html

https://www.cnblogs.com/cxjchen/p/3148582.html

https://blog.csdn.net/realxie/article/details/7090493

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(C++,设计模式)