怎么在C++項目中實現(xiàn)一個單例模式

本篇文章給大家分享的是有關(guān)怎么在C++項目中實現(xiàn)一個單例模式,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

成都創(chuàng)新互聯(lián)公司10多年成都定制網(wǎng)站服務(wù);為您提供網(wǎng)站建設(shè),網(wǎng)站制作,網(wǎng)頁設(shè)計及高端網(wǎng)站定制服務(wù),成都定制網(wǎng)站及推廣,對三輪攪拌車等多個領(lǐng)域擁有豐富的網(wǎng)站運維經(jīng)驗的網(wǎng)站建設(shè)公司。

單例模式

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

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

考慮到這些需要,我們將默認的構(gòu)造函數(shù)聲明為私有的,這樣就不會被外部所new了,甚至可以將析構(gòu)函數(shù)也聲明為私有的,這樣就只有自己能夠刪除自己了。在Java和C#這樣純的面向?qū)ο蟮恼Z言中,單例模式非常好實現(xiàn),直接就可以在靜態(tài)區(qū)初始化instance,然后通過getInstance返回,這種就被稱為餓漢式單例類。也有些寫法是在getInstance中new instance然后返回,這種就被稱為懶漢式單例類,但這涉及到第一次getInstance的一個判斷問題。

下面的代碼只是表示一下,跟具體哪種語言沒有關(guān)系。

單線程中:

Singleton* getInstance()
{
  if (instance == NULL)
    instance = new Singleton();
 
  return instance;
}

這樣就可以了,保證只取得了一個實例。但是在多線程的環(huán)境下卻不行了,因為很可能兩個線程同時運行到if (instance == NULL)這一句,導致可能會產(chǎn)生兩個實例。于是就要在代碼中加鎖。

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)的線程才會有進入鎖定臨界區(qū)的可能性,這種幾率還是比較低的,不會阻塞太多的線程,但為了防止一個線程進入臨界區(qū)創(chuàng)建實例,另外的線程也進去臨界區(qū)創(chuàng)建實例,又加上了一道防御if (instance == NULL),這樣就確保不會重復創(chuàng)建了。

怎么在C++項目中實現(xiàn)一個單例模式

常用的場景

單例模式常常與工廠模式結(jié)合使用,因為工廠只需要創(chuàng)建產(chǎn)品實例就可以了,在多線程的環(huán)境下也不會造成任何的沖突,因此只需要一個工廠實例就可以了。

優(yōu)點
1.減少了時間和空間的開銷(new實例的開銷)。

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

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

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

C++實現(xiàn)代碼

#ifndef _SINGLETON_H_
#define _SINGLETON_H_


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

private:
Singleton();
//把復制構(gòu)造函數(shù)和=操作符也設(shè)為私有,防止被復制
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;
}

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

運行結(jié)果

怎么在C++項目中實現(xiàn)一個單例模式

下面給大家補充一下

單例模式有兩種實現(xiàn)模式:

1)懶漢模式: 就是說當你第一次使用時才創(chuàng)建一個唯一的實例對象,從而實現(xiàn)延遲加載的效果。

2)餓漢模式: 就是說不管你將來用不用,程序啟動時就創(chuàng)建一個唯一的實例對象。

所以,從實現(xiàn)手法上看, 懶漢模式是在第一次使用單例對象時才完成初始化工作。因為此時可能存在多線程競態(tài)環(huán)境,如不加鎖限制會導致重復構(gòu)造或構(gòu)造不完全問題。

餓漢模式則是利用外部變量,在進入程序入口函數(shù)之前就完成單例對象的初始化工作,此時是單線程所以不會存在多線程的競態(tài)環(huán)境,故而無需加鎖。

以下是典型的幾種實現(xiàn)

一、 懶漢模式,標準的 ”雙檢鎖“ + ”自動回收“ 實現(xiàn)

class Singleton

{
public:
  static Singleton* GetInstance()
  {
    if (m_pInstance == NULL )
    {
      Lock(); // 加鎖
      if (m_pInstance == NULL )
      {
        m_pInstance = new Singleton ();
      }
      UnLock(); // 解鎖
    }
    return m_pInstance;
  }

  // 實現(xiàn)一個內(nèi)嵌垃圾回收類  
  class CGarbo 
  {
  public:
    ~CGarbo()
    {
      if(Singleton::m_pInstance) 
        delete Singleton::m_pInstance;
    }
  };

  static CGarbo Garbo; // 定義一個靜態(tài)成員變量,程序結(jié)束時,系統(tǒng)會自動調(diào)用它的析構(gòu)函數(shù)從而釋放單例對象

private:
  Singleton(){};
  Singleton(Singleton const&); 
  Singleton& operator=(Singleton const&); 

  static Singleton* m_pInstance;
};

Singleton* Singleton::m_pInstance = NULL;
Singleton::CGarbo Garbo;

二、靜態(tài)局部變量的懶漢模式 ,而不是new在堆上創(chuàng)建對象,避免自己回收資源。

這里仍然要注意的是局部變量初始化的線程安全性問題,在C++0X以后,要求編譯器保證靜態(tài)變量初始化的線程安全性,可以不加鎖。但C++ 0X以前,仍需要加鎖。

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 &);
};

在懶漢模式里,如果大量并發(fā)線程獲取單例對象,在進行頻繁加鎖解鎖操作時,必然導致效率低下。

三、餓漢模式,基礎(chǔ)版本

因為程序一開始就完成了單例對象的初始化,所以后續(xù)不再需要考慮多線程安全性問題,就可以避免懶漢模式里頻繁加鎖解鎖帶來的開銷。

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; // 在程序入口之前就完成單例對象的初始化

雖然這種實現(xiàn)在一定程度下能良好工作,但是在某些情況下會帶來問題 --- 就是在C++中 ”非局部靜態(tài)對象“ 的 ”初始化“ 順序 的 ”不確定性“, 參見Effective c++ 條款47。

考慮: 如果有兩個這樣的單例類,將分別生成單例對象A, 單例對象B. 它們分別定義在不同的編譯單元(cpp中), 而A的初始化依賴于B 【 即A的構(gòu)造函數(shù)中要調(diào)用B::GetInstance() ,而此時B::m_instance 可能還未初始化,顯然調(diào)用結(jié)果就是非法的 】, 所以說只有B在A之前完成初始化程序才能正確運行,而這種跨編譯單元的初始化順序編譯器是無法保證的。

四、餓漢模式,增強版本(boost實現(xiàn))

在前面的方案中:餓漢模式中,使用到了類靜態(tài)成員變量,但是遇到了初始化順序的問題; 懶漢模式中,使用到了靜態(tài)局部變量,但是存在著線程安全等問題。

boost 的實現(xiàn)方式是:單例對象作為靜態(tài)局部變量,然后增加一個輔助類,并聲明一個該輔助類的類靜態(tài)成員變量,在該輔助類的構(gòu)造函數(shù)中,初始化單例對象。以下為代碼

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;

首先,代理類這個外部變量初始化時,在其構(gòu)造函數(shù)內(nèi)部調(diào)用 Singleton::GetInstance();從而間接完成單例對象的初始化,這就通過該代理類實現(xiàn)了餓漢模式的特性。

其次,仍然考慮第三種模式的缺陷。 當A的初始化依賴于B, 【 即A的構(gòu)造函數(shù)中要調(diào)用B::GetInstance() ,而此時B::m_instance 可能還未初始化,顯然調(diào)用結(jié)果就是非法的 】 現(xiàn)在就變?yōu)椤驹贏的構(gòu)造函數(shù)中要調(diào)用B::GetInstance() ,如果B尚未初始化,就會引發(fā)B的初始化】,所以在不同編譯單元內(nèi)全局變量的初始化順序不定的問題就隨之解決。

最后,關(guān)于使用懶漢還是餓漢模式,我的理解:

如果這個單例對象構(gòu)造十分耗時或者占用很多資源,比如加載插件啊, 初始化網(wǎng)絡(luò)連接啊,讀取文件啊等等,而有可能該對象程序運行時不會用到,那么也要在程序一開始就進行初始化,也是一種資源浪費吧。 所以這種情況懶漢模式(延遲加載)更好。

以上就是怎么在C++項目中實現(xiàn)一個單例模式,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降?。希望你能通過這篇文章學到更多知識。更多詳情敬請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

網(wǎng)頁標題:怎么在C++項目中實現(xiàn)一個單例模式
URL鏈接:http://bm7419.com/article6/geieig.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供移動網(wǎng)站建設(shè)、動態(tài)網(wǎng)站響應(yīng)式網(wǎng)站、App設(shè)計、微信小程序、云服務(wù)器

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站優(yōu)化排名