單例模式(重點*)
單例模式是23種常用設計模式中最簡單的設計模式之一,它提供了一種創建對象的方式,確保只有單個對象被創建。這個設計模式主要目的是想在整個系統中只能出現類的一個實例,即一個類只有一個對象。
將單例對象創建在靜態區
根據已經學過的知識進行分析:
-
將構造函數私有;
-
通過靜態成員函數getInstance創建局部靜態對象,確保對象的生命周期和唯一性;
-
getInstance的返回值設為引用,避免復制;
隱患:如果單例對象所占空間較大,可能會對靜態區造成內存壓力。
class Point
{
public://定義為靜態函數是因為要創建對象而調用成員函數又需要對象來調用,所以就將成員函數定義為靜態成員函數直接使用類來進項調用// 使用& 是因為防止返回發生拷貝構造static Point & getInstance(){static Point pt(1,2);return pt;}void print() const{cout << "(" << this->_ix<< "," << this->_iy<< ")" << endl;}private:Point(int x,int y): _ix(x), _iy(y){cout << "Point(int,int)" << endl;}
private:int _ix;int _iy;
};void test0(){//使用&來接收就不會發生拷貝構造//這是pt和pt2指向就是相同的Point & pt = Point::getInstance();pt.print();Point & pt2 = Point::getInstance();pt2.print();cout << &pt << endl;cout << &pt2 << endl;
}
將單例對象創建在堆區
既然將單例對象創建在全局/靜態區可能會有內存壓力,那么為這個單例對象動態分配空間是比較合理的選擇。請嘗試實現代碼:
分析:
-
構造函數私有;
-
因為非靜態對象沒有唯一性,所以我們要人為加一個判斷語句來看是否第一次調用getInstance函數,所以在類中聲明一個靜態成員(因為我們想用它在靜態成員函數中做判斷)來接收通過靜態成員函數getInstance創建堆上的對象,返回Point*類型的指針,如果該靜態成員_pInstance為空就可以創建對象;
-
通過靜態成員函數完成堆對象的回收。
? ? ? ? ? ? 多個指針指向同一塊空間,是比較危險的,如果我像上面代碼那樣將代碼進行回收了,我在用其他指向原來那塊空間的指針來訪問就會出問題,所以為了避免問題,就使用下面的單例模式的規范。
可能會對析構函數產生誤解,析構函數是用來回收數據成員申請的堆空間的,而上面的數據成員并沒有申請堆空間。
假如是將代碼加到析構函數中使用析構函數來回收空間會怎么樣呢?
調用析構函數,進入if判斷不為nullptr,調用delete,而delete的第一步又是調用析構函數,這樣就進去無限的循環直到棧的空間被占滿。
這里不使用注釋那樣來調用destory是因為destory一開始設計不是static,需要對象來調用,而且這也還會再一次調用創建對象的成員函數,所以直接將destory設計為static,直接使用類名來調用。
上面定義的是一個死的數據因為是單例模式只能讓他進行一次初始化,我們如果想要使單例的數據可以修改呢?
我們可以將上面的getInstance的構造函數改為無參構造只進行空間的申請,不對空間的數據進行初始化,然后通過這個函數的返回值再次調用初始化數據函數(init),使數據可以進行修改。
單例對象的數據成員申請堆空間
要求:實現一個單例的Computer類,包含品牌和價格信息。
#include <string.h>
#include <iostream>
using std ::cout;
using std ::endl;
class Computer
{
public:static Computer *getInstance(){if (_pInstance == nullptr)_pInstance = new Computer();return _pInstance;}static void destroy(){if (_pInstance){delete _pInstance;_pInstance = nullptr;}cout << "heap delete" << endl;}void init(const char *brand, double price){if (_brand){delete[] _brand;_brand = nullptr;}_brand = new char[strlen(brand) + 1]();strcpy(_brand, brand);_price = price;}void print(){cout << _brand << endl;cout << _price << endl;}private:// 構造函數Computer() {};Computer(const char *brand, double price): _brand(new char[strlen(brand) + 1]()), _price(price){strcpy(_brand, brand);}// 析構函數~Computer(){if (_brand){delete _brand;_brand = nullptr;}cout << "~Computer" << endl;}Computer(const Computer &rhs) = delete;Computer &operator=(const Computer &rhs) = delete;char *_brand;double _price;static Computer *_pInstance;
};
Computer *Computer ::_pInstance = nullptr;int main()
{Computer ::getInstance()->init("bob", 2222);Computer ::getInstance()->print();Computer ::getInstance()->init("tom", 6666);Computer ::getInstance()->print();Computer ::destroy();return 0;
}
單例模式的應用場景
1、有頻繁實例化然后銷毀的情況,也就是頻繁的 new 對象,可以考慮單例模式;
2、創建對象時耗時過多或者耗資源過多,但又經常用到的對象;
3、當某個資源需要在整個程序中只有一個實例時,可以使用單例模式進行管理(全局資源管理)。例如數據庫連接池、日志記錄器等;
4、當需要讀取和管理程序配置文件時,可以使用單例模式確保只有一個實例來管理配置文件的讀取和寫入操作(配置文件管理);
5、在多線程編程中,線程池是一種常見的設計模式。使用單例模式可以確保只有一個線程池實例,方便管理和控制線程的創建和銷毀;
6、GUI應用程序中的全局狀態管理:在GUI應用程序中,可能需要管理一些全局狀態,例如用戶信息、應用程序配置等。使用單例模式可以確保全局狀態的唯一性和一致性。