C++單例模式原理與實現

今天有一個同學問我單例模式的實現方式,就順手寫篇文章記錄一下。

單例模式,可以說設計模式中最常應用的一種模式了,據說也是面試官最喜歡的題目。但是如果沒有學過設計模式的人,可能不會想到要去應用單例模式,面對單例模式適用的情況,可能會優先考慮使用全局或者靜態變量的方式,這樣比較簡單,也是沒學過設計模式的人所能想到的最簡單的方式了。

一般情況下,我們建立的一些類是屬於工具性質的,基本不用存儲太多的跟自身有關的數據,在這種情況下,每次都去new一個對象,即增加了開銷,也使得代碼更加臃腫。其實,我們只需要一個實例對象就可以。如果採用全局或者靜態變量的方式,會影響封裝性,難以保證別的代碼不會對全局變量造成影響。

考慮到這些需要,我們將默認的構造函數聲明爲私有的,這樣就不會被外部所new了,甚至可以將析構函數也聲明爲私有的,這樣就只有自己能夠刪除自己了。在Java和C#這樣純的面向對象的語言中,單例模式非常好實現,直接就可以在靜態區初始化instance,然後通過getInstance返回,這種就被稱爲餓漢式單例類。也有些寫法是在getInstance中new instance然後返回,這種就被稱爲懶漢式單例類,但這涉及到第一次getInstance的一個判斷問題。

下面的實現過程其實與什麼語言沒有太大關係,關鍵是思想,什麼語言都一樣。

單線程中:

1
2
3
4
5
6
7
Singleton* getInstance()
{
    if (instance == NULL)
        instance = new Singleton();
 
    return instance;
}
這樣就可以了,保證只取得了一個實例。但是在多線程的環境下卻不行了,因爲很可能兩個線程同時運行到if (instance == NULL)這一句,導致可能會產生兩個實例。於是就要在代碼中加鎖。
Singleton* getInstance()
{
    lock();
    if (instance == NULL)
    {
       instance = new Singleton();
    }
    unlock();

    return instance;
}
但這樣寫的話,會稍稍映像性能,因爲每次判斷是否爲空都需要被鎖定,如果有很多線程的話,就愛會造成大量線程的阻塞。於是大神們又想出了雙重鎖定
Singleton* getInstance()
{
    if (instance == NULL)
    {
	lock();
    	if (instance == NULL)
    	{
       		instance = new Singleton();
    	}
    	unlock();
    }

    return instance;
}
這樣只夠極低的機率下,通過越過了if (instance == NULL)的線程纔會有進入鎖定臨界區的可能性,這種機率還是比較低的,不會阻塞太多的線程,但爲了防止一個線程進入臨界區創建實例,另外的線程也進去臨界區創建實例,又加上了一道防禦if (instance == NULL),這樣就確保不會重複創建了。

下面的圖片清晰顯示:

image_thumb[3]

 

常用的場景

單例模式常常與工廠模式結合使用,因爲工廠只需要創建產品實例就可以了,在多線程的環境下也不會造成任何的衝突,因此只需要一個工廠實例就可以了。

 

優點

1.減少了時間和空間的開銷(new實例的開銷)。

2.提高了封裝性,使得外部不易改動實例。

 

缺點

1.懶漢式是以時間換空間的方式。

2.餓漢式是以空間換時間的方式。

C++實現代碼

#ifndef _SINGLETON_H_
#define _SINGLETON_H_


class Singleton{
public:
	static Singleton* getInstance();

private:
	Singleton();
	//把複製構造函數和=操作符也設爲私有,防止被複制
	Singleton(const Singleton&);
	Singleton& operator=(const Singleton&);

	static Singleton* instance;
};

#endif

 

#include "Singleton.h"


Singleton::Singleton(){

}


Singleton::Singleton(const Singleton&){

}


Singleton& Singleton::operator=(const Singleton&){

}


//在此處初始化
Singleton* Singleton::instance = new Singleton();
Singleton* Singleton::getInstance(){
	return instance;
}

 

#include "Singleton.h"
#include <stdio.h>


int main(){
	Singleton* singleton1 = Singleton::getInstance();
	Singleton* singleton2 = Singleton::getInstance();

	if (singleton1 == singleton2)
		fprintf(stderr,"singleton1 = singleton2\n");

	return 0;
}

採用g++編譯:

g++ -o client Singleton.cpp client.cpp

image_thumb[3]

 

常用的場景

單例模式常常與工廠模式結合使用,因爲工廠只需要創建產品實例就可以了,在多線程的環境下也不會造成任何的衝突,因此只需要一個工廠實例就可以了。

 

優點

1.減少了時間和空間的開銷(new實例的開銷)。

2.提高了封裝性,使得外部不易改動實例。

 

缺點

1.懶漢式是以時間換空間的方式。

2.餓漢式是以空間換時間的方式。

發佈了32 篇原創文章 · 獲贊 25 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章