設計模式:單例模式(Singleton Pattern)

文章目錄

    • 一、單例模式的概念
    • 二、單例模式的結構
    • 三、常見實現方式
      • 3.1 餓漢式單例
      • 3.2 懶漢式單例

一、單例模式的概念

單例模式(Singleton Pattern)是一種創建型設計模式,它的核心思想是:保證在一個進程中,某個類僅有一個實例,并提供全局訪問點。

在這里插入圖片描述
問題:
單例模式同時解決了兩個問題, 所以違反了單一職責原則:
1.保證一個類只有一個實例。 為什么會有人想要控制一個類所擁有的實例數量? 最常見的 原因是控制某些共享資源 (例如數據庫或文件) 的訪問權限。

它的運作方式是這樣的: 如果你創建了一個對象, 同時過一會兒后你決定再創建一個新對象, 此時你會獲得之前已創建的對象, 而不是一個新對象。

注意, 普通構造函數無法實現上述行為, 因為構造函數的設計決定了它必須總是返回一個新對象。

在這里插入圖片描述

客戶端甚至可能沒有意識到它們一直都在使用同一個對象。

2.為該實例提供一個全局訪問節點。 還記得你用過的那些存儲重要對象的全局變量嗎? 它們在使用上十分方便, 但同時也非常不安全, 因為任何代碼都有可能覆蓋掉那些變量的內容, 從而引發程序崩潰。

和全局變量一樣, 單例模式也允許在程序的任何地方訪問特定對象。 但是它可以保護該實例不被其他代碼覆蓋。

還有一點: 你不會希望解決同一個問題的代碼分散在程序各處的。 因此更好的方式是將其放在同一個類中, 特別是當其他代碼已經依賴這個類時更應該如此。

常見應用場景:

  • 日志系統:全局唯一的日志記錄器。
  • 配置管理:全局讀取配置文件。
  • 線程池、數據庫連接池:全局資源管理。
  • 設備驅動對象:保證唯一控制入口。

單例模式的關鍵要素:

  • 構造函數私有化:防止外部隨意 new。
  • 拷貝構造與賦值運算符刪除:防止復制對象。
  • 靜態成員指針/對象:存儲唯一實例。
  • 公共靜態方法:提供獲取實例的入口。

二、單例模式的結構

所有單例的實現都包含以下兩個相同的步驟:

  • 將默認構造函數設為私有, 防止其他對象使用單例類的 new運算符。
  • 新建一個靜態構建方法作為構造函數。 該函數會 “偷偷” 調用私有構造函數來創建對象, 并將其保存在一個靜態成員變量中。 此后所有對于該函數的調用都將返回這一緩存對象。

如果你的代碼能夠訪問單例類, 那它就能調用單例類的靜態方法。 無論何時調用該方法, 它總是會返回相同的對象。

在這里插入圖片描述

三、常見實現方式

  1. 在類中添加一個私有靜態成員變量用于保存單例實例。
  2. 聲明一個公有靜態構建方法用于獲取單例實例。
  3. 在靜態方法中實現"延遲初始化"。 該方法會在首次被調用時創建一個新對象, 并將其存儲在靜態成員變量中。 此后該方法每次被調用時都返回該實例。
  4. 將類的構造函數設為私有。 類的靜態方法仍能調用構造函數, 但是其他對象不能調用。
  5. 檢查客戶端代碼, 將對單例的構造函數的調用替換為對其靜態構建方法的調用。

函數內靜態變量初始化的線程安全問題:
在 C++98/03 中:

  • 函數內的靜態局部變量在第一次調用函數時初始化。
  • 多線程調用時,如果兩個線程同時進入函數,就可能同時執行初始化,造成多次構造,屬于競態條件(Race Condition)。

所以在 C++98/03 里,需要手動加鎖才能保證安全。

C++11 標準明確規定:函數內靜態局部變量在第一次初始化時,初始化過程是線程安全的。
也就是說:

  • 即使多個線程同時調用 getInstance(),只有一個線程會執行構造函數。
  • 其他線程會等待構造完成,然后使用同一個實例。

3.1 餓漢式單例

餓漢式的特點:

  • 類加載時就創建實例,不管你用不用它,它都存在。
  • 線程安全:因為實例在程序開始時就初始化了,不存在多線程并發創建的問題。
  • 資源消耗:如果對象很大而一直沒用到,會浪費內存。
  • 適用場景:對象創建成本低,且在程序運行中幾乎一定會用到。

靜態成員對象

#include <iostream>class Singleton {
private:Singleton() { std::cout << "Singleton Created\n"; } // 構造函數私有化~Singleton() { std::cout << "Singleton Destroyed\n"; }Singleton(const Singleton&) = delete;               // 禁止拷貝Singleton& operator=(const Singleton&) = delete;    // 禁止賦值static Singleton instance;   // 靜態成員,類加載時即初始化public:static Singleton& getInstance() {return instance;         // 返回唯一實例}void show() {std::cout << "Hello Hungry Singleton!" << std::endl;}
};// 靜態成員初始化(在程序啟動時創建)
Singleton Singleton::instance;int main() {Singleton& s1 = Singleton::getInstance();Singleton& s2 = Singleton::getInstance();s1.show();std::cout << "s1 addr = " << &s1 << ", s2 addr = " << &s2 << std::endl;return 0;
}

輸出結果:
在這里插入圖片描述
實現原理(簡要):

  • 編譯器在生成代碼時,會在靜態變量前加上一次性標志(guard variable)。
  • 第一個線程進入函數時,檢查標志:
    • 如果未初始化 → 執行構造函數 → 設置標志
    • 如果已初始化 → 直接返回實例
  • 編譯器會保證對標志的寫入和檢查是原子操作或通過內部鎖完成,從而保證線程安全。

3.2 懶漢式單例

懶漢式的特點:

  • 延遲初始化:只有第一次調用 getInstance() 時才創建對象。
  • 線程安全問題:多線程環境下,需要注意可能出現多個實例的問題。
  • 優點:節省資源,只有在真正需要時才創建。
  • 缺點:實現稍復雜,需要考慮多線程安全。

懶漢式實現(C++11 推薦寫法)
C++11 后,使用 函數內靜態變量 可以保證線程安全:

#include <iostream>class Singleton {
private:Singleton() { std::cout << "Singleton Created\n"; } // 構造私有~Singleton() { std::cout << "Singleton Destroyed\n"; }Singleton(const Singleton&) = delete;               // 禁止拷貝Singleton& operator=(const Singleton&) = delete;    // 禁止賦值public:static Singleton& getInstance() {static Singleton instance;  // 第一次調用時創建,C++11線程安全return instance;}void show() {std::cout << "Hello Lazy Singleton!" << std::endl;}
};int main() {Singleton& s1 = Singleton::getInstance();Singleton& s2 = Singleton::getInstance();s1.show();std::cout << "s1 addr = " << &s1 << ", s2 addr = " << &s2 << std::endl;return 0;
}

輸出結果:
在這里插入圖片描述
懶漢式的多線程安全寫法(C++11 前)
如果在 C++11 之前,需要手動加鎖防止多線程同時創建多個實例:

#include <mutex>class Singleton {
private:Singleton() {}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;static Singleton* instance;static std::mutex mtx;public:static Singleton* getInstance() {if (instance == nullptr) {std::lock_guard<std::mutex> lock(mtx);if (instance == nullptr) {instance = new Singleton();}}return instance;}
};// 靜態成員初始化
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

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

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

相關文章

Swift 解法詳解 LeetCode 362:敲擊計數器,讓數據統計更高效

文章目錄 摘要 描述 題解答案 題解代碼分析 代碼講解 示例測試及結果 時間復雜度 空間復雜度 總結 摘要 “敲擊計數器”這道題聽上去像個小游戲里的功能,但其實它背后對應的是一個常見的需求:在過去一段時間內統計事件發生的次數。比如網站的訪問量統計、API 調用次數限制、…

coze工作流200+源碼,涵蓋AI文案生成、圖像處理、視頻生成、自動化腳本等多個領域

AI 博主風哥在github分享了 200 實用生產力coze工作流&#xff0c;涵蓋AI文案生成、圖像處理、視頻生成、自動化腳本等多個領域&#xff0c;導入即用&#xff0c;項目地址https://github.com/Hammer1/cozeworkflows github下載慢也可前往該地址下載https://pan.baidu.com/s/1fC…

AI與SEO關鍵詞協同優化

內容概要 人工智能&#xff08;AI&#xff09;技術的迅猛發展正深刻變革著搜索引擎優化&#xff08;SEO&#xff09;的實踐方式&#xff0c;特別是在關鍵詞策略這一核心領域。兩者的深度融合&#xff0c;為企業在數字海洋中精準導航提供了前所未有的強大工具。通過AI驅動的智能…

【Unity開發】Unity核心學習(二)

二、動畫基礎 1、Animation動畫窗口 &#xff08;1&#xff09;介紹&#xff08;2&#xff09;Animation窗口功能2、創建編輯動畫 面板變化&#xff1a;動畫文件界面&#xff1a;3、Animator動畫狀態機 &#xff08;1&#xff09;有限狀態機概念&#xff08;2&#xff09;Anima…

NETSDK1045 當前 .NET SDK 不支持將 .NET 8.0 設置為目標。請將 .NET 5.0 或更低版本設置為目標,或使用支持

C# 項目中的目標框架無法修改并且顯示為空 嚴重性 代碼 說明 項目 文件 行 禁止顯示狀態 錯誤 NETSDK1045 當前 .NET SDK 不支持將 .NET 8.0 設置為目標。請將 .NET 5.0 或更低版本設置為目標&#xff0c;或使用支持 .NET 8.0 的 .NET SDK 版本。 Padim C:\Program …

MNIST 數據集mnist.npz詳解

MNIST 數據集是機器學習領域最著名的數據集之一&#xff0c;全稱為"Modified National Institute of Standards and Technology"數據庫。它包含了大量手寫數字的圖像&#xff0c;是入門機器學習和深度學習的經典數據集。1. MNIST 數據集概述 60,000 張訓練圖像 10,00…

深入理解HTTPS:從概念到實戰優化

深入理解HTTPS&#xff1a;從概念到實戰優化一&#xff1a;概述二&#xff1a;工作流程三&#xff1a;創建自簽名證書四&#xff1a;案例1&#xff09;案例一&#xff1a;HTTPS 搭建2&#xff09;案例二&#xff1a;HTTP/2 搭建3&#xff09;案例三&#xff1a;HTTP 重定向 HTT…

MySQL數據備份與恢復全攻略

一、數據備份與恢復按照備份方式分類&#xff1a;物理備份&#xff0c;直接復制數據庫的物理文件&#xff0c;可以直接拷貝和恢復&#xff1b;邏輯備份&#xff0c;通過SQL語句導出數據庫結構和數據&#xff0c;可用于不同版本和不同類型的MySQL數據庫之間的數據遷移。按照數據…

單機多卡間大張量傳輸迷惑行為?

老鐵們我最近真的好慘&#x1f62d;&#xff0c;一個大模型在單機多卡上運行就是出錯&#xff0c;debug看的老眼昏花&#xff0c;最后發現大張量在設備間直接傳輸會有很發癲的行為&#xff0c;還請大家幫我看看&#x1f647;?摒棄屎山一樣的代碼&#xff0c;簡單運行下列腳本i…

無法將“pnpm”項識別為 cmdlet、函數、腳本文件或可運行程序的名稱。請檢查名稱的拼寫,如果包括路徑,請確保路徑正確,然后再試一次。

1 問題描述今天使用pnpm安裝如下報錯&#xff1a;pnpm : 無法將“pnpm”項識別為 cmdlet、函數、腳本文件或可運行程序的名稱。請檢查名稱的拼寫&#xff0c;如果包括路徑&#xff0c;請確保路徑正確&#xff0c;然后再試一次。 所在位置 行:1 字符: 1pnpm install~~~~ Categor…

內核編譯 day61

二&#xff1a;內核啟動 一&#xff1a;流程 sudo vim /etc/network/interfaces sudo chmod 0666 /etc/default/tft...... //修改可讀uboot命令&#xff1a; help/&#xff1f; &#xff1a; 幫助手冊&#xff0c;列出uboot支持的所有命令 printenv/print 打印環境變量 sete…

【YOLOv5部署至RK3588】模型訓練→轉換RKNN→開發板部署

已在GitHub開源與本博客同步的YOLOv5_RK3588_object_detect項目&#xff0c;地址&#xff1a;https://github.com/A7bert777/YOLOv5_RK3588_object_detect/tree/main 詳細使用教程&#xff0c;可參考README.md或參考本博客第六章 模型部署 文章目錄一、項目回顧二、模型選擇介紹…

Telematics Control Unit(TCU)的系統化梳理

1、Telematics Control Unit (TCU)概述 TCU中文名為遠程信息處理控制單元&#xff0c;很多場合都稱為Telematics Box&#xff0c;又叫TBox&#xff0c;顧名思義&#xff0c;一般都為一個獨立的盒子&#xff08;如圖2、圖3所示&#xff09;&#xff0c;負責和云端的遠程信息交互…

Appium學習筆記

adb構成client端&#xff0c;在電腦上&#xff0c;負責發送adb命令daemon守護進程&#xff0c;在手機上&#xff0c;負責接收和執行adb命令server端&#xff0c;在電腦上&#xff0c;負責管理client和daemon之間的通信![[Pasted image 20250825201322.png]]包名&#xff0c;對應…

棧指針(Stack Pointer)是什么?

棧指針(Stack Pointer)是什么? 首先,用一個簡單易懂的方式解釋棧指針(Stack Pointer)。 核心比喻:摞起來的書 想象有一摞書整齊地堆在桌面上: 這摞書就是“棧”(Stack),它是一種后進先出(LIFO) 的數據結構。你只能從最頂部拿走一本書(“彈出”),或者把一本新…

數據結構:紅黑樹(Red-Black Tree)

目錄 從AVL樹的“煩惱”說起 如何用“顏色”來定義“大致平衡”&#xff1f;—— 紅黑樹的五個規則 五個規則如何保證“大致平衡”&#xff1f; 用 C/C 代碼定義紅黑樹的結構 定義顏色和節點結構 定義樹的結構和哨兵節點 從AVL樹的“煩惱”說起 我們從已經了解的 AVL 樹出…

Ubuntu22.04安裝VMware Tools

文章目錄前言安裝open-mv-tools前言 本教程使用的版本是Ubuntu22.04.5&#xff0c;由于虛擬機上面的重新安裝VMware Tools是灰的&#xff0c;于是自動下載安裝open-mv-tools&#xff0c; 安裝open-mv-tools 打開終端&#xff0c;更新一下 sudo apt update這一步可能需要先…

DBeaver連接SQL Server時添加驅動后仍提示找不到驅動的解決方法

DBeaver連接SQL Server時添加驅動后仍提示找不到驅動的解決方法 在使用DBeaver連接SQL Server時&#xff0c;即使您已手動添加驅動文件&#xff0c;系統仍提示“找不到驅動”&#xff0c;這通常是由驅動配置錯誤、版本不兼容或SQL Server設置問題引起的。以下我將逐步為您提供解…

JVM之【類加載系統】

目錄 前言 類加載過程 類加載 執行過程 加載階段 連接階段 初始化階段 類加載器 BootstrapClassLoader ExtClassLoader AppClassLoader 類加載器之間的關系 雙親委派機制 核心思想 好處 源碼分析 類加載器之間的父子層級關系 雙親委派的體現 前言 上文中提到…

【 限流技術 | 從四大限流算法到Redisson令牌桶實踐 】

引言&#xff1a;為什么需要限流&#xff1f;在現代分布式系統中&#xff0c;服務的穩定性是至關重要的。在遇到突發的請求量激增&#xff0c;惡意的用戶訪問&#xff0c;亦或是請求頻率過高給下游服務帶來較大壓力時&#xff0c;我們常常需要通過緩存、限流、熔斷降級、負載均…