【C++】特殊類設計 {不能被拷貝的類;只能在堆上創建的類;只能在棧上創建的類;不能被繼承的類;單例模式:懶漢模式,餓漢模式}

一、不能被拷貝的類

設計思路:

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

C++98方案:
將拷貝構造與賦值重載只聲明不定義,并且將其訪問權限設置為私有即可。

class CopyBan
{// ...
private:CopyBan(const CopyBan&);CopyBan& operator=(const CopyBan&);//...
};

原因:

  1. 設置成私有:如果只聲明沒有設置成private,用戶自己如果在類外定義了,就不能禁止拷貝了。

  2. 只聲明不定義:不定義是因為該函數根本不會調用,定義了其實也沒有什么意義,不寫反而還簡單,而且如果定義了就不會防止成員函數內部拷貝了。

C++11方案:
C++11擴展delete的用法,delete除了釋放new申請的資源外,如果在默認成員函數后跟上=delete,表示讓編譯器刪除掉該默認成員函數。

class CopyBan
{// ...CopyBan(const CopyBan&)=delete;CopyBan& operator=(const CopyBan&)=delete;//...
};

二、只能在堆上創建的類

思路一:將構造、拷貝構造函數私有

  1. 將類的構造、拷貝構造聲明成私有。
  2. 提供一個靜態的成員函數,在該靜態成員函數中使用new申請堆空間并調用構造函數完成堆對象的初始化,最后返回該對象的指針。
class HeapOnly
{int _val;// 把構造和拷貝構造設置成私有HeapOnly(int val = 0): _val(val){}// 一定要把拷貝構造也設為私有HeapOnly(const HeapOnly &obj);public:// 提供一個靜態的成員函數,使用new申請堆空間并調用構造函數完成堆對象的創建。static HeapOnly *CreateObj(int val = 0){return new HeapOnly(val);}
};int main()
{// HeapOnly obj;HeapOnly *pobj1 = HeapOnly::CreateObj(10);// HeapOnly obj(*pobj1);return 0;
}

思路二:將析構函數私有

編譯器在為類對象分配棧空間時,會先檢查類的構造和析構函數的訪問性。由于棧的創建和釋放都需要由系統完成的,所以若是無法調用構造或者析構函數,自然會報錯。如果類的析構函數是私有的,則編譯器將報錯。

當然為了我們能夠釋放動態創建的對象,我們必須提供一個公有函數,該函數的唯一功能就是刪除堆對象。

  1. 將類的析構函數聲明成私有。
  2. 提供一個公有的成員函數,執行delete this調用析構函數清理對象資源并釋放堆空間。
class HeapOnly
{int _val;// 把析構設置成私有~HeapOnly(){cout << "~HeapOnly()" << endl;}public:HeapOnly(int val = 0): _val(val){}// 提供一個公有的成員函數,執行delete this調用析構函數清理對象資源并釋放堆空間void DestroyObj(){delete this;}
};int main()
{// HeapOnly obj;HeapOnly *pobj = new HeapOnly(10);// HeapOnly obj(*pobj);// delete pobj;pobj->DestroyObj();return 0;
}

三、只能在棧上創建的類

思路:重載operator new

我們還可以將new操作符重載并設置為私有訪問。

class StackOnly
{int _val;void* operator new(size_t t);
public:StackOnly(int val = 0): _val(val){}StackOnly(const StackOnly &obj): _val(obj._val){}
};int main()
{StackOnly obj(10);StackOnly obj1(obj);// StackOnly *pobj = new StackOnly(10);// StackOnly *pobj1 = new StackOnly(obj);return 0;
}

四、不能被繼承的類

C++98方案:將構造函數私有

派生類中調不到基類的構造函數,則無法繼承。

class NonInherit
{
public:static NonInherit CreatObj(){return NonInherit();}
private:NonInherit(){}
};

C++11方案:final關鍵字

final修飾類,表示該類不能被繼承。

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

五、單例模式

5.1 設計模式

設計模式(Design Pattern)是一套被反復使用、多數人知曉的、經過分類的代碼設計經驗總結。

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

常用的設計模式:

  1. 適配器模式:對已有的類進行適配包裝形成具有全新功能和性質的類,如:棧、隊列、優先級隊列、function包裝器。
  2. 迭代器模式:幾乎所有容器通用的遍歷訪問方式,可以封裝隱藏容器的底層結構,以類似指針的使用方式訪問容器中的數據。如:數組(vector)、鏈表(list)、哈希表(unordered_map)、樹(map)的迭代器。
  3. 單例模式:接下來的內容
  4. 工廠模式:工廠模式是一種創建對象的設計模式,它通過定義一個工廠類來封裝對象的創建過程,并通過調用工廠類的方法來創建對象,從而將對象的創建與使用分離。
  5. 觀察者模式:觀察者模式是一種對象間的一對多依賴關系,當一個對象的狀態發生變化時,它的所有依賴者都會得到通知并自動更新。

單例模式:

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

5.2 餓漢模式

所謂餓漢模式,就是說不管你將來用不用,程序啟動時(main函數之前)就創建一個唯一的實例對象。

方法一:在堆區創建單例

設計思路:

  1. 私有構造、拷貝構造和析構,保證系統中該類只有一個實例;
  2. 包含一個該類的靜態指針并在類外使用new創建單例,提供一個訪問單例的全局訪問點;
  3. 包含一個互斥鎖成員,保證多線程互斥訪問該單例;
  4. 提供一個用于獲取全局訪問點(靜態指針)的靜態成員函數;
  5. 包含一個靜態的內部類對象,該對象析構時會順便析構單例,自動釋放。
class Singleton
{// 成員變量vector<string> _dir;// 該類的靜態指針,提供一個訪問單例的全局訪問點static Singleton *s_ins;// 互斥鎖成員,保證多線程互斥訪問該單例mutex s_mtx;// 靜態的內部類對象,該對象析構時會順便析構單例,自動釋放struct GC{~GC(){if (s_ins != nullptr){delete s_ins;s_ins = nullptr;}}};static GC s_gc;// 私有構造、拷貝構造和析構,保證系統中該類只有一個實例Singleton(){cout << "Singleton()" << endl;};Singleton(const Singleton &st);~Singleton(){// 單例對象的析構一般會做一些持久化操作(數據落盤)// ......cout << "~Singleton()" << endl;}public:// 提供一個靜態成員函數,用于獲取全局訪問點(靜態指針)static Singleton *GetInstance(){return s_ins;}void Add(const string &name){s_mtx.lock();_dir.push_back(name);s_mtx.unlock();}void Print(){s_mtx.lock();for (auto &name : _dir){cout << name << endl;}s_mtx.unlock();}
};// 程序啟動時(main函數之前)創建
Singleton *Singleton::s_ins = new Singleton;
Singleton::GC Singleton::s_gc;int main()
{// 系統中該類只有一個實例,不允許通過任何方式實例化// Singleton st;// static Singleton st1;// Singleton* pst = new Singleton;//  Singleton st(*(Singleton::GetInstance()));// 單線程場景// Singleton::GetInstance()->Add("張三");// Singleton::GetInstance()->Add("李四");// Singleton::GetInstance()->Add("王五");// Singleton::GetInstance()->Print();// 多線程場景int n = 6;srand((unsigned int)time(nullptr));thread t1([n]() mutable{while(n--){Singleton::GetInstance()->Add("線程1:" + to_string(rand()));this_thread::sleep_for(chrono::milliseconds(10));} });thread t2([n]() mutable{while(n--){Singleton::GetInstance()->Add("線程2:" + to_string(rand()));this_thread::sleep_for(chrono::milliseconds(10));} });t1.join();t2.join();Singleton::GetInstance()->Print();
}

運行結果(多線程場景):

在這里插入圖片描述


方法二:在靜態區創建單例

設計思路:

  1. 私有構造、拷貝構造和析構,保證系統中該類只有一個實例;
  2. 包含一個該類的靜態對象并在類外定義,提供一個訪問單例的全局訪問點;
  3. 包含一個互斥鎖成員,保證多線程互斥訪問該單例;
  4. 提供一個用于獲取全局訪問點(靜態對象的引用)的靜態成員函數;
  5. 由于單例是在靜態區創建的,進程結束時,系統會自動調用單例析構釋放其資源。
// 餓漢模式2
class Singleton
{// 成員變量vector<string> _dir;// 該類的靜態對象,提供一個訪問單例的全局訪問點static Singleton s_ins;// 互斥鎖成員,保證多線程互斥訪問該單例mutex s_mtx;// 私有構造、拷貝構造和析構,保證系統中該類只有一個實例Singleton(){cout << "Singleton()" << endl;};Singleton(const Singleton &st);// 由于單例是在靜態區創建的,進程結束時,系統會自動調用單例析構釋放其資源。~Singleton(){// 單例對象的析構一般會做一些持久化操作(數據落盤)// ......cout << "~Singleton()" << endl;}public:// 提供一個靜態成員函數,用于獲取全局訪問點(靜態對象的引用)static Singleton &GetInstance(){return s_ins;}void Add(const string &name){s_mtx.lock();_dir.push_back(name);s_mtx.unlock();}void Print(){s_mtx.lock();for (auto &name : _dir){cout << name << endl;}s_mtx.unlock();}  
};// 程序啟動時(main函數之前)創建
Singleton Singleton::s_ins;

運行結果:同上

餓漢模式的缺點:

  1. 由于單例對象是在main函數之前創建的,如果單例對象很大,很復雜,其創建和初始化所占用的時間較多。會拖慢程序的啟動速度。
  2. 如果當前進程暫時不需要使用該單例對象,而餓漢模式在啟動時創建單例占用了空間和時間資源。
  3. 如果具有依賴關系的兩個單例都是餓漢模式,需要先創建單例1再創建單例2。餓漢模式無法控制其創建和初始化順序。

提示:餓漢模式的全局訪問點除了定義靜態指針還可以直接定義成靜態對象。如果是靜態對象,進程在退出時會自動調用其析構函數。


5.3 懶漢模式

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

所謂懶漢模式,就是在任意程序模塊第一次訪問單例時實例化對象。

方法一:在堆區創建單例

設計思路:

  1. 私有構造、拷貝構造和析構,保證系統中該類只有一個實例;
  2. 包含一個該類的靜態指針并在類外初始化為nullptr,提供一個訪問單例的全局訪問點;
  3. 包含一個靜態互斥鎖并在類外定義,保證多線程互斥地創建和訪問該單例;
  4. 提供一個靜態成員函數,用于首次調用創建單例(注意雙檢查加鎖)和獲取全局訪問點(靜態指針);
  5. 包含一個靜態的內部類對象,該對象析構時會順便析構單例,自動釋放。
// 懶漢模式
class Singleton
{// 成員變量vector<string> _dir;// 該類的靜態指針,提供一個訪問單例的全局訪問點static Singleton *s_ins;// 靜態互斥鎖,保證多線程互斥地創建和訪問該單例static mutex s_mtx;// 靜態的內部類對象,該對象析構時會順便析構單例,自動釋放struct GC{~GC(){if (s_ins != nullptr){delete s_ins;s_ins = nullptr;}}};static GC gc;// 私有構造、拷貝構造和析構,保證系統中該類只有一個實例Singleton(){cout << "Singleton()" << endl;};Singleton(const Singleton &st);~Singleton(){// 單例對象的析構一般會做一些持久化操作(數據落盤)// ......cout << "~Singleton()" << endl;}
public:static Singleton *GetInstance(){// 懶漢模式:在第一次訪問實例時創建// 雙檢查加鎖if (s_ins == nullptr) // 第一道檢查:提高效率,不需要每次獲取單例都加鎖解鎖{s_mtx.lock();if (s_ins == nullptr) // 第二道檢查:保證線程安全和只new一次{s_ins = new Singleton;}s_mtx.unlock();}return s_ins;}void Add(const string &name){s_mtx.lock();_dir.push_back(name);s_mtx.unlock();}void Print(){s_mtx.lock();for (auto &name : _dir){cout << name << endl;}s_mtx.unlock();}// 一般單例對象的生命周期隨進程,系統會在進程退出時釋放其內存,不需要中途析構單例對象// 不過在一些特殊場景下,可能需要進行顯示手動釋放static void DelInstance(){s_mtx.lock();if (s_ins != nullptr){delete s_ins;s_ins = nullptr;}s_mtx.unlock();}
};// 靜態成員要在類外定義
Singleton *Singleton::s_ins = nullptr;
mutex Singleton::s_mtx;
Singleton::GC Singleton::gc;

運行結果(多線程場景):

在這里插入圖片描述


方法二:在靜態區創建單例(C++11)

設計思路:

  1. 私有構造、拷貝構造和析構,保證系統中該類只有一個實例;
  2. 提供一個靜態成員函數,用于首次調用創建單例(創建靜態局部對象)和獲取全局訪問點(靜態對象的指針);
  3. 包含一個互斥鎖成員,保證多線程互斥訪問該單例;
  4. 由于單例是在靜態區創建的,進程結束時,系統會自動調用單例析構釋放其資源。
// 懶漢模式2
class Singleton
{// 成員變量vector<string> _dir;// 互斥鎖成員,保證多線程互斥訪問該單例mutex s_mtx;// 私有構造、拷貝構造和析構,保證系統中該類只有一個實例Singleton(){cout << "Singleton()" << endl;};Singleton(const Singleton &st);~Singleton(){// 單例對象的析構一般會做一些持久化操作(數據落盤)// ......cout << "~Singleton()" << endl;}public:static Singleton *GetInstance(){// C++11之前,這里不能保證初始化靜態對象的線程安全問題// C++11之后,這里可以保證初始化靜態對象的線程安全問題static Singleton s_ins; //首次調用時創建局部靜態對象return &s_ins;}void Add(const string &name){s_mtx.lock();_dir.push_back(name);s_mtx.unlock();}void Print(){s_mtx.lock();for (auto &name : _dir){cout << name << endl;}s_mtx.unlock();}
};

運行結果:同上

懶漢模式模式完美解決了餓漢模式的問題,就是相對復雜一些。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/162302.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/162302.shtml
英文地址,請注明出處:http://en.pswp.cn/news/162302.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

FDG6306P PowerTrench? MOSFET P溝道 特點及其應用詳解

關于PowerTrench MOSFET&#xff1f; 它是一種MOS場效應晶體管&#xff0c;可以提高系統效率和功率密度。該技術采用了屏蔽柵極技術&#xff0c;可以減少開關損耗和導通損耗&#xff0c;從而提高了系統效率。此外&#xff0c;PowerTrench MOSFET還具有低導通電阻和高開關速度的…

三角洲雜志三角洲雜志社三角洲編輯部2023年第19期目錄

作家在線 李明聰 把寫作當成一種享受 李明聰; 2 頭條作品 冬天的童話 王排; 5-7 迎來春色換人間 王排; 8《三角洲》投稿&#xff1a;cnqikantg126.com 小說精選 鋼哥 曹茂炯; 9-25 重逢 莫艷陽; 26 散文現場 孩子&#xff0c;你相信光嗎&#xff1f; 趙…

前端js語音朗讀文本

<!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>語音朗讀</title></head><body>&l…

如何滿足BMW EDI項目的PKT需求?

近期寶馬BMW&#xff08;以下簡稱BMW&#xff09;在其部分供應商之間試點推進PKT項目&#xff0c;BMW為什么要啟動 PKT 計劃呢&#xff1f; 業務系統全面升級統一全球所有寶馬工廠的流程 寶馬內部的物流供貨流程 近期BMW PKT需求主要針對其內部物流供貨流程展開&#xff1a; …

嵌入式開發--賽普拉斯cypress的鐵電存儲器FM25CL64B

嵌入式開發–賽普拉斯cypress的鐵電存儲器FM25CL64B 簡介 FM25CL64B是賽普拉斯cypress出品的一款鐵電存儲器&#xff0c;這種存儲器最大的優勢是可以像RAM一樣隨機存儲&#xff0c;和按字節寫入&#xff0c;也可以像ROM一樣掉電仍然可以保存數據&#xff0c;是一種相當優秀的…

Redis 持久化機制

client Redis[內存] --> 內存數據、磁盤數據----> 磁盤&#xff0c;Redis官方提供了兩種不同的持久化方案將內存中的數據存儲在硬盤中&#xff1a; 快照&#xff08;Snapshot&#xff09; AOF只追加日志文件。 1、快照&#xff08;Snapshot&#xff09; 1、快照的特點…

如何用CHAT解釋文章含義?

問CHAT&#xff1a;解釋“ 本身樂善好施&#xff0c;令名遠近共欽&#xff0c;待等二十左右&#xff0c;定有高親可攀&#xff1b;而且四德俱備&#xff0c;幫夫之緣亦有。主持家事不紊&#xff0c;上下亦無閑言。但四十交進&#xff0c;家內謹防口舌&#xff0c;須安家堂&…

分布式篇---第一篇

系列文章目錄 文章目錄 系列文章目錄前言一、分布式冪等性如何設計?二、簡單一次完整的 HTTP 請求所經歷的步驟?三、說說你對分布式事務的了解前言 前些天發現了一個巨牛的人工智能學習網站,通俗易懂,風趣幽默,忍不住分享一下給大家。點擊跳轉到網站,這篇文章男女通用,…

非遺之光:十八數藏柏松數字保護的璀璨之路

隨著數字技術的崛起&#xff0c;非物質文化遺產的保護進入了一個新的紀元。在這個時代的先鋒中&#xff0c;十八數藏以其對傳統工藝的數字保護而獨領風騷。這是一條璀璨之路&#xff0c;通過數字技術的應用&#xff0c;為傳統工藝注入了新的活力。 十八數藏柏松將數字創新融入傳…

軟件包管理器yum和git

目錄 一、Linux軟件包管理器yum 1、Linux下的軟件安裝方法 2、了解yum 1、實際例子引入 2、yum 3、查找軟件包 4、安裝軟件包 5、卸載軟件 二、git 一、Linux軟件包管理器yum 1、Linux下的軟件安裝方法 1、在Linux下安裝軟件&#xff0c;一個通常的辦法是下載到程序的源…

c 一維數組轉為二維數組

通過數組指針來轉換 用這種方法可以把屏幕mmap 中的數據轉為二維的長乘高的數據 #include <stdio.h>int main() {int mm[5] { 0,1,2,3,4 };int (*pm)[3] (int (*)[3])mm; //pm 排 &#xff0c;[3]表示列printf("%d\n", pm[0][2]); // {0,1,2}…

經典百搭女童加絨衛衣,看的見的時尚

經典版型套頭衛衣 寬松百搭不挑人穿 單穿內搭都可以 胸口處有精美的小熊印花 面料是復合柔軟奧利絨 暖和又不顯臃腫哦&#xff01;&#xff01;

Jenkins+Maven+Gitlab+Tomcat 自動化構建打包、部署

JenkinsMavenGitlabTomcat 自動化構建打包、部署 1、環境需求 本帖針對的是Linux環境&#xff0c;Windows或其他系統也可借鑒。具體只講述Jenkins配置以及整個流程的實現。 1.JDK&#xff08;或JRE&#xff09;及Java環境變量配置&#xff0c;我用的是JDK1.8.0_144&#xff0…

排序算法--快速排序

實現邏輯 ① 從數列中挑出一個元素&#xff0c;稱為 “基準”&#xff08;pivot&#xff09;&#xff0c; ② 重新排序數列&#xff0c;所有元素比基準值小的擺放在基準前面&#xff0c;所有元素比基準值大的擺在基準的后面&#xff08;相同的數可以到任一邊&#xff09;。在這…

LoRa技術-什么是LoRa

1 概述 LoRa是創建長距離通信連接的物理層無線調制技術&#xff0c;屬于CCS&#xff08;線性調制擴頻技術&#xff09;的一種&#xff0c;工作頻段范圍在Sub-1GHz以下。相較于傳統的FSK等技術&#xff0c;LoRa在保持低功耗的同時極大地增加了通訊距離&#xff0c;且具備抗干擾…

2023年度openGauss標桿應用實踐案例征集

標桿應用實踐案例征集 2023 openGauss 數據庫作為企業IT系統的核心組成部分&#xff0c;是數字基礎設施建設的關鍵&#xff0c;是實現數據安全穩定的保障。openGauss順應開源發展趨勢&#xff0c;強化核心技術突破&#xff0c;著力打造自主根社區&#xff0c;攜手產業伙伴共同…

【開源】基于JAVA的高校實驗室管理系統

項目編號&#xff1a; S 015 &#xff0c;文末獲取源碼。 \color{red}{項目編號&#xff1a;S015&#xff0c;文末獲取源碼。} 項目編號&#xff1a;S015&#xff0c;文末獲取源碼。 目錄 一、摘要1.1 項目介紹1.2 項目錄屏 二、研究內容2.1 實驗室類型模塊2.2 實驗室模塊2.3 實…

這個問題你必須關注!網上申請的流量卡未激活是否可以更換套餐?

資費低&#xff0c;流量多&#xff0c;所以近年來在網上申請流量卡已經成了一種趨勢&#xff0c;雖然在網上申請流量卡比較方便&#xff0c;但是很多問題大家都比較迷惑&#xff0c;就比如&#xff0c;下面有私信小編的一個問題。 ?  網友咨詢&#xff0c;網上申請的流量卡…

PTA-用天平找小球

三個球A、B、C&#xff0c;大小形狀相同且其中有一個球與其他球重量不同。要求找出這個不一樣的球。 輸入格式&#xff1a; 輸入在一行中給出3個正整數&#xff0c;順序對應球A、B、C的重量。 輸出格式&#xff1a; 在一行中輸出唯一的那個不一樣的球。 輸入樣例&#xff…

內衣洗衣機哪些品牌質量好實惠?小型洗衣機全自動

現在洗內衣內褲也是一件較麻煩的事情了&#xff0c;在清洗過程中還要用熱水殺菌&#xff0c;還要確保洗衣液是否有沖洗干凈&#xff0c;還要防止細菌的滋生等等&#xff0c;所以入手一款小型的烘洗全套的內衣洗衣機是非常有必要的&#xff0c;專門的內衣洗衣機可以最大程度減少…