日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C++單例設計模式詳細講解_C 語言

作者:愛生活,愛代碼 ? 更新時間: 2022-08-03 編程語言

特殊類設計

只能在堆上創建對象的類

請設計一個類,只能在堆上創建對象

實現方式:

  • 將類的構造函數私有,拷貝構造聲明成私有。防止別人調用拷貝在棧上生成對象。
  • 提供一個靜態的成員函數,在該靜態成員函數中完成堆對象的創建
class test 
{
public:
	static test* GetObj() 
	{
		return new test();  //堆上申請并創建一個對象
	}
private:
	//構造函數私有
	test() { cout << "調用了構造函數" << endl; }
	//拷貝構造私有化,無法實例化對象
	test(const test& obj){};
};
void func1() 
{
	test* p = test::GetObj();  //通過調用靜態函數獲取對象的指針
}

請設計一個類只能在棧上創建對象

方法一:同上將構造函數私有化,然后設計靜態方法創建對象返回即可。

//只能在棧上創建對象
class test1 
{
public:
	static test1 GetObj()
	{
		return test1(); //棧上創建一個對象并返回
	}
	test1() { cout << "調用了構造函數" << endl; }
private:
	//拷貝構造私有化無法實例化對象
	test1(const test1& obj) {  }
};

方法二:屏蔽new,因為new在底層調用void* operator new(size_t size)函數(會在堆上開辟空間),我們只需要在類里面自定義定位new和delete就會不再new的時候調用全局的operator new和operator delete 最后該函數私有化,也就防止了在堆上創建對象,注意:也要防止定位new

class test02 
{
public:
	test02() 
	{ }
private:
	//自定義的定位new、定位delete
	void *operator new(size_t size) 
	{ 
		//代碼....  
	}
	void operator delete(void *p)  
	{  
		//代碼....
	}
};

請設計一個類不能被拷貝

拷貝只會放生在兩個場景中:拷貝構造函數以及賦值運算符重載,因此想要讓一個類禁止拷貝,只需讓該類不能調用拷貝構造函數以及賦值運算符重載即可。

C++98將拷貝構造函數與賦值運算符重載只聲明不定義,并且將其訪問權限設置為私有即可

原因:

  • 設置成私有:如果只聲明沒有設置成private,用戶自己如果在類外定義了,就可以不能禁止拷貝了
  • 只聲明不定義:不定義是因為該函數根本不會調用,定義了其實也沒有什么意義,不寫反而還簡單,而且如果定義了就不會防止成員函數內部拷貝了。
  • C++11的寫法也可以在 默認成員函數后面加上delete,表示讓編譯器刪除掉該默認成員函數
class CopyBan
{
 // ...
private:
 CopyBan(const CopyBan&);
 CopyBan& operator=(const CopyBan&);
 //...
};

請設計一個類不能被繼承

C++98會對父類的構造函數私有化,但是這個方法并不夠徹底,實際上是子類還是繼承了父類的,只是沒辦法實例化對象,那么也就沒有意義了

class test
{
private:
	test(){}
}

C++11方法

final關鍵字,final修飾類,表示該類不能被繼承

class A final
{
 // ....
};

請設計一個類只能創建一個對象(單例模式)

設計模式(Design Pattern)是一套被反復使用、多數人知曉的、經過分類的、代碼設計經驗的總結。為什么會產生設計模式這樣的東西呢?就像人類歷史發展會產生兵法。最開始部落之間打仗時都是人拼人的對砍。后來春秋戰國時期,七國之間經常打仗,就發現打仗也是有套路的,后來孫子就總結出了《孫子兵法》。孫子兵法也是類似。

使用設計模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。 設計模式使代碼編寫真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構一樣。

單例模式:

一個類只能創建一個對象,即單例模式,該模式可以保證系統中該類只有一個實例,并提供一個訪問它的全局訪問點,該實例被所有程序模塊共享。比如在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,然后服務進程中的其他對象再通過這個單例對象獲取這些配置信息,這種方式簡化了在復雜環境下的配置管理。

單例模式有兩種實現模式:

餓漢模式、就是說不管你將來用不用,程序啟動時就創建一個唯一的實例對象,如果這個單例對象在多線程高并發環境下頻繁使用,性能要求較高,那么顯然使用餓漢模式來避免資源競爭,提高響應速度更好。

實現方法:在類里面定義一個靜態的成員指針obj,通過提供一個靜態的成員函數獲取該指針obj

class Singleton 
{
public:
	static Singleton* GetInstance()
	{
		return obj;
	}
private:
	Singleton() {}
	Singleton(const Singleton& obj) {}
	static Singleton* obj;
};
Singleton* Singleton::obj = new Singlet	on; 

懶漢模式、如果在套用單例設計模式,單例對象構造十分耗時或者占用很多資源,比如加載插件啊, 初始化網絡連接啊,讀取文件啊等等,而有可能該對象程序運行時不會用到,那么也要在程序一開始就進行初始化,就會導致程序啟動時非常的緩慢。 所以這種情況使用懶漢模式(延遲加載)更好。

懶漢模式:我們需要考慮到多線程的安全隱患,和處理的返回值指針指向的永遠都是同一個對象的指針,這樣子才不會失去單例對象的性質,主要改變的是static Singleton* GetInstance();接口函數的設計

static Singleton* GetInstance()
{
	//如果只有一層的if判斷的我們的程序就會存在一定的問題,
	//在多線程的場景下,多個線程都是比較自由的,可讀可寫、或者只讀、只寫
	//這樣在一些場景下就會導致單例對象失去他本身的性質,
	//比如:兩個線程 th1、th2,同時進入到了if語句中,假設th1會先執行。
	//為obj 申請了一個對象并返回它的指針,難么這個實例對象也就創建了
	//最后return obj,th1結束后(不確定結束時間),
	//th2又會再次new一個對象給obj,最后導致obj被賦值兩次
	//整個過程梳理:
	//1、th1線程存在內存泄漏,因為obj被賦值兩次,而第一次new出來的對象并沒有被釋放,那么就存在了內存泄漏
	//2、obj被賦值兩次,失去單例對象性質
	if (!obj) 
	{
		if (!obj) 
		{
			obj = new Singleton();
		}
	}
	return obj;  //返回對象的指針
}

改善一:引入C++11線程庫,加入互斥鎖管理線程

static Singleton* GetInstance()
{
		//通過加鎖,保證了線程安全
		m_mtx.lock();  //加鎖
		//假設th1先拿到鎖,那么th1就會先執行下面的語句,而th2就會等待,
		//obj就會指向new出來的單例對象,最后th1解鎖完了return ,而th2
		//才剛拿到鎖繼續if判斷的時候obj已經有值了就會跳過if繼續往下執
		//行,然后解鎖最終return ,整個過程中單例對象只有一份,
		//不存在二次賦值
		if (!obj) 
		{
			obj = new Singleton();
		}
		m_mtx.unlock();  //解鎖
	return obj;  //返回對象的指針
}

進一步優化

因為第一次加鎖解鎖之后,處理了線程安全的問題,而往后的obj指針已經被初始化了,也就不需要再new一次,所以可以再最外層套上一層if判斷,防止繼續枷鎖解鎖,因為頻繁的加鎖解鎖會導致線程不斷的切入切出有上下文切換的開銷

static Singleton* GetInstance()
{
	if(!obj)  
		//第一次判斷保護線程安全,第二次obj已經有值,不需要執行{....}
		//優點:保證線程安全的同時完成了單例對象的初始化
	{
		m_mtx.lock();  //加鎖
		if (!obj)   //保證單例對象只有一份,不會存在二次賦值
		{
			obj = new Singleton();
		}
		m_mtx.unlock();  //解鎖
	}
	return obj;  //返回對象的指針
}

完善實現

//懶漢
class Singleton 
{
public:
	class CGarbo {
	public:
		~CGarbo() {
			if (obj)   //釋放對象指針,并置空
			{
				delete obj;
				obj = nullptr;
			}
		}
	};
public:
	static Singleton* GetInstance()
	{
		//雙重檢查的好處是保護了線程安全,同時又提高了效率
		if (!obj) 
		{
			m_mtx.lock();  //加鎖
			if (!obj) 
			{
				obj = new Singleton();
			}
			m_mtx.unlock();  //解鎖
		}
		return obj;  //返回對象的指針
	}
private:
	Singleton()  {}
	Singleton(const Singleton& obj) {}
	void operator=(const Singleton& obj) {}
	static Singleton* obj; //聲明對象指針
	static mutex m_mtx; //聲明互斥鎖
	static CGarbo Garbo;  //聲明垃圾回收器對象
};
Singleton* Singleton::obj = nullptr;  //對象指針初始化
mutex Singleton::m_mtx;  //定義互斥鎖
Singleton::CGarbo Garbo;  //定義垃圾回收器對象

懶漢模式和餓漢模式的對比

餓漢

優點:簡單

缺點:1、如果單例對象構造函數工作比較多,會導致程序啟動慢,遲遲進不了入口main函數

2、如果有多個單例對象,他們之間有初始化依賴關系,餓漢模式也會有問題。

比如有A和B兩個單例類,要求A單例先初始化,B必須在A之后初始化。那么餓漢無法保證

這種場景下面用懶漢就可以,懶漢可以先調用A::GetInstance(),再調用B::GetInstance().

懶漢

優點:解決上面餓漢的缺點。因為他是第一次調用GetInstance時創建初始化單例對象

缺點:相對餓漢,復雜一點點。

迭代器模式、適配器模式

設計模式有興趣的同學,可以下去再看看工廠模式、觀察者模式等等

原文鏈接:https://blog.csdn.net/m0_53421868/article/details/122715617

欄目分類
最近更新