設計模式 | 單例模式——餓漢模式 懶漢模式

單例模式

文章目錄

  • 單例模式
    • 一、餓漢模式(Eager Initialization)
      • 1. 定義
      • 2. 特點
      • 3. 餓漢單例模式(定義時-類外初始化)
      • 4. 實現細節
    • 二、懶漢模式(Lazy Initialization)
      • 1. 定義
      • 2. 特點
      • 3. 懶漢單例模式(第一次調用時-初始化)
      • 4. 多線程不安全(需加鎖)
    • 三、對比 & 使用建議

一、餓漢模式(Eager Initialization)

1. 定義

類加載時就創建實例,不管你用不用,先創建再說。

2. 特點

  • 線程安全(因為類加載是線程安全的)
  • 啟動時就分配資源,資源消耗可能較大

3. 餓漢單例模式(定義時-類外初始化)

#include <iostream>class TaskQueue {
public:// 靜態方法:獲取唯一實例指針static TaskQueue* getInstance() {return m_taskQ;  // 返回靜態成員變量指針}// 刪除拷貝構造函數:防止復制實例(例如 TaskQueue b = a)TaskQueue(const TaskQueue&) = delete;// 刪除賦值運算符:防止賦值復制(例如 a = b)TaskQueue& operator=(const TaskQueue&) = delete;private:// 默認構造函數私有化:禁止類外部構造對象// 外部無法通過 new TaskQueue() 或 TaskQueue t; 構造對象TaskQueue() = default;// 靜態成員變量聲明:用于保存唯一實例的指針static TaskQueue* m_taskQ;
};// ?? 類外定義并初始化靜態成員變量:這一行非常關鍵!
// ? 這是 TaskQueue 類的“靜態成員變量定義+初始化”
// ? new TaskQueue 調用了 private 構造函數,但因為這是類自己的代碼(初始化自己的靜態成員),所以**允許訪問私有成員**
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;
// --------------------------------------------
// ?? 雖然這行“寫在類外”(語法上),但它是類的一部分(靜態成員初始化),它仍然被認為是類自己的代碼(類內部行為),所以可以訪問私有構造函數。
// C++ 標準允許它訪問類的 private 構造函數。
// 所以不會報錯,而是合法的。int main() {// 獲取單例對象的指針TaskQueue* q1 = TaskQueue::getInstance();TaskQueue* q2 = TaskQueue::getInstance();// 打印地址驗證是否為同一實例std::cout << "q1 地址: " << q1 << std::endl;std::cout << "q2 地址: " << q2 << std::endl;// 輸出地址肯定一樣return 0;
}

注意:

// 靜態成員變量定義和初始化(在類外完成)
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;

這句代碼在程序啟動時就執行,立即創建了 TaskQueue 的唯一實例:

  1. 是靜態變量,生命周期貫穿整個程序;
  2. 實例在任何 getInstance() 調用之前就已創建完成;
  3. getInstance() 只是簡單地返回這個已創建好的指針。

因此,它就是一個標準的餓漢單例模式實現。

4. 實現細節

  1. 為什么TaskQueue* TaskQueue::m_taskQ = new TaskQueue;屬于類內訪問,可以訪問private構造函數?
    是因為它是“靜態變量”?“私有變量”?還是“初始化”這件事本身?
條件是否是關鍵解釋
? 這是類的成員定義? 是關鍵初始化 TaskQueue::m_taskQ 是類的一部分,因此有權限訪問類的私有成員
static 成員? 不是核心原因雖然需要類外初始化,但并不是 static 帶來了訪問權限
private 變量? 更不是原因private 表示“只能被類的代碼訪問”,而這行被視為類的代碼

不管是 static 還是 private,關鍵原因在于:這是類的“成員變量定義”,屬于類的內部實現,因此它擁有類的訪問權限。

  1. 延申:若把 new TaskQueue 寫在 main()

? 非法代碼(main 函數中訪問私有構造函數)

int main() {TaskQueue* q = new TaskQueue();  // ? 錯!構造函數是 private
}

為什么報錯?

  • main() 是類外部的普通代碼。
  • 它不是類成員,不被視為類內部實現。
  • 因此沒有權限訪問私有構造函數,編譯器會直接報錯。

? 合法代碼(類外定義靜態成員時調用私有構造函數)

TaskQueue* TaskQueue::m_taskQ = new TaskQueue;  // ? 對!

為什么合法?

  • 這是類在定義和初始化自己的靜態成員變量。
  • 雖然代碼寫在類外,但它被視為類的一部分(屬于 TaskQueue 類實現)。
  • 所以有權訪問 private 構造函數。
  • C++ 語法明確允許這種訪問。

二、懶漢模式(Lazy Initialization)

1. 定義

在第一次訪問時才創建實例,延遲到真正需要的時候再進行初始化。

2. 特點

  • 延遲加載:只有在首次調用 getInstance() 時才會創建實例,節省系統資源;
  • 線程不安全(默認實現),但可以通過加鎖、雙重檢查、std::call_once 等方式實現線程安全
  • 相較于餓漢模式,更靈活、更節省資源,但實現稍復雜。

3. 懶漢單例模式(第一次調用時-初始化)

#include <iostream>class TaskQueue {
public:// ? 沒有加鎖,線程不安全 ******不同點******static TaskQueue* getInstance() {if (m_taskQ == nullptr) {    m_taskQ = new TaskQueue(); // ?不安全,可能多個線程同時執行這里,創建多個實例}return m_taskQ;}TaskQueue(const TaskQueue&) = delete;TaskQueue& operator=(const TaskQueue&) = delete;private:TaskQueue() = default;static TaskQueue* m_taskQ;
};// 初始化靜態實例指針 ******不同點******
TaskQueue* TaskQueue::m_taskQ = nullptr;int main() {TaskQueue* q1 = TaskQueue::getInstance();TaskQueue* q2 = TaskQueue::getInstance();std::cout << "q1 地址: " << q1 << std::endl;std::cout << "q2 地址: " << q2 << std::endl;// 輸出地址一樣(如果線程不沖突)return 0;
}

4. 多線程不安全(需加鎖)

線程沖突時,多個線程可能在getInstance()創建多個對象,需要加鎖!!!

三、對比 & 使用建議

對比項餓漢模式(Eager Singleton)懶漢模式(Lazy Singleton)
實例創建時機程序啟動時 / 類加載時立即創建第一次調用 getInstance() 時才創建
資源占用無論是否使用都會占用資源僅在需要時才占用資源,更節省內存
線程安全? 天然線程安全(由 C++ 靜態初始化保證)? 默認線程不安全,需手動加鎖處理
實現難度實現簡單,邏輯清晰實現復雜(涉及鎖、雙檢、或 call_once)
性能開銷啟動時略高,占用資源可能浪費每次調用 getInstance() 可能涉及鎖(效率略低)
適用場景實例始終會用到,資源占用可接受實例可能不一定會用到,或實例化代價較高
常用實現類外初始化靜態成員指針(如:new Singleton;內部判斷是否為 null + 加鎖后 new Singleton();
示例構造代碼TaskQueue* m = new TaskQueue;(類外直接構造)if (!m) m = new TaskQueue;(函數內延遲構造)
可擴展性不容易擴展為參數化構造初始化時可自定義參數(但需額外設計)
使用場景推薦模式
實例一定會被頻繁使用? 餓漢模式(簡單穩定)
實例創建代價高或可能不用? 懶漢模式(延遲創建)
多線程訪問高頻? 餓漢 或 call_once 懶漢
希望按需控制生命周期? 懶漢更靈活

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

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

相關文章

dify本地部署及添加ollama模型(ubuntu24.04)

說明&#xff1a;ubuntu是虛擬機的&#xff0c;用的橋接模式&#xff0c;與本地同局域網不同ip地址。 參考VM虛擬機網絡配置&#xff08;ubuntu24橋接模式&#xff09;&#xff1a;配置靜態IP前提&#xff1a;需要有docker及docker-compose環境 參考ubuntu24安裝docker及docker…

Python爬蟲實戰:研究multiprocessing相關技術

一、引言 1.1 研究背景與意義 隨著互聯網信息的爆炸式增長,網絡爬蟲已成為獲取海量數據的重要工具。傳統的單線程爬蟲在面對大規模數據采集任務時效率低下,無法充分利用現代計算機多核 CPU 的優勢。多線程爬蟲雖然在一定程度上提高了效率,但受限于 Python 的全局解釋器鎖(…

6.18 redis面試題 日志 緩存淘汰過期刪除 集群

Redis有哪2種持久化方式&#xff1f;分別的優缺點是什么&#xff1f; Redis 的重寫 AOF 過程是由后臺子進程 bgrewriteaof 來完成的。 過期刪除策略和內存淘汰策略有什么區別&#xff1f; 內存淘汰策略是在內存滿了的時候&#xff0c;redis 會觸發內存淘汰策略&#xff0c;來淘…

什么時候會發生內存泄漏?

1. 內存泄漏是什么&#xff1f; 定義&#xff1a;內存泄漏是指程序中的對象已經不再需要&#xff0c;但由于被其他對象錯誤引用&#xff0c;導致垃圾回收器&#xff08;GC&#xff09;無法回收它&#xff0c;從而長期占用內存空間的現象。 2. 內存泄漏的危害 問題具體表現內存…

用RSA算法模擬類的適配器模式

“RAS算法”這個術語本身并不常見或標準&#xff0c;它可能指向兩個主要領域的不同概念&#xff0c;具體取決于上下文&#xff1a; 更可能是拼寫錯誤&#xff1a;指 RSA 算法&#xff08;密碼學&#xff09; 這是最常見的情況。 “RAS” 極有可能是 “RSA” 的拼寫錯誤。RSA 算…

CARSIM-與C#自動化測試方案

using System; using System.Runtime.InteropServices; using System.Collections.Generic;namespace CarSimAutomation {/// <summary>/// CarSim COM 自動化測試接口/// 封裝所有 CarSim COM 功能用于自動化測試/// </summary>[ComVisible(true)][ClassInterface…

企微CRM系統中的任務分配與效率提升技巧

在數字化管理時代&#xff0c;企業微信(企微)與CRM系統的深度融合&#xff0c;為企業提供了更高效的客戶管理與團隊協作方案。企微CRM軟件不僅整合了客戶溝通、銷售跟進、數據分析等功能&#xff0c;還能通過智能任務分配優化團隊效率。本文將深入探討企微CRM管理系統的任務分配…

day66—BFS—最短的橋(LeetCode-934)

題目描述 給你一個大小為 n x n 的二元矩陣 grid &#xff0c;其中 1 表示陸地&#xff0c;0 表示水域。 島 是由四面相連的 1 形成的一個最大組&#xff0c;即不會與非組內的任何其他 1 相連。grid 中 恰好存在兩座島 。 你可以將任意數量的 0 變為 1 &#xff0c;以使兩座…

FramePack 安裝指南(中文)

FramePack 安裝指南&#xff08;中文&#xff09; -Windows FramePack 是最前沿的 AI 視頻生成框架&#xff0c;以極小的硬件需求顛覆視頻創作&#xff01;它能在僅 6GB 筆記本 GPU 內存上&#xff0c;驅動 13B 模型以 30 FPS 生成超長 120 秒視頻&#xff0c;幾乎無內容限制&…

Redis Sentinel 非集群模式高可用部署指南

1. Sentinel 在非集群模式的定位 一句話&#xff1a;在單主多從架構中&#xff0c;用 Sentinel 替你盯哨——探測故障、選舉新主、通知客戶端。 核心四職能&#xff1a; 職能作用點Monitoring定時 PING 主從&#xff0c;自身也互相探測Notification通過日志/PubSub/外部調用報…

2025Java面試八股文

文章目錄 Java基礎JVM多線程SpringSpring Boot數據庫與SQL分布式系統其他 Java基礎 自動裝箱與拆箱&#xff1a;Java中基礎數據類型與包裝類之間的轉換。例如&#xff0c;Integer x 1; 是裝箱&#xff0c;int y x; 是拆箱。Object類常用方法&#xff1a;如clone()、getClass…

寶塔安裝nginx-rtmp,音視頻直播

前置&#xff1a;需要自己開發音視頻直播&#xff0c; 注意不是實時音視頻&#xff0c;不是一對一視頻聊天&#xff0c;不是視頻會議 方案有 srs &#xff0c;nginx-rtmp&#xff0c;live555&#xff0c;node-media-server&#xff0c;EasyDarwin等 今天是說 nginx-rtmp 怎么…

基于微信小程序和深度學習的寵物照片拍攝指導平臺的設計與實現

文章目錄 摘要前言緒論1. 課題背景2. 國內外現狀與趨勢2.1 國內研究現狀2.2 國外研究現狀2.3 發展趨勢3. 課題內容相關技術與方法介紹1. 微信小程序開發技術2. 深度學習模型選型2.1 MobileNetV22.2 ResNet-503. 系統架構設計4. 關鍵技術實現4.1 實時拍攝指導4.2 多模態建議生成…

web布局02

Web 發展的每個不同時期都有新的技術為 Web 布局提供支持&#xff0c;但不管是哪個時期&#xff0c;Web 布局相關的概念和術語都是相同的。如果你想徹底或者更好地掌握 Web 布局&#xff0c;那么首先需要對 Web 布局相關的技術術語有所了解。 在這一節中&#xff0c;我們一起來…

Mac電腦 窗口分屏管理 Magnet Pro

Magnet Pro Mac&#xff0c;是一款功能強大的窗口分屏管理工具&#xff0c;具有多種布局模式、窗口布局功能和其他工具&#xff0c;可以幫助您高效地進行多任務處理和管理工作。 拖動窗口到邊緣&#xff0c;可將窗口大小調整到屏幕的一半。拖動窗口到角落&#xff0c;可將窗口…

http2與websocket關系

HTTP/2 和 WebSocket 協議本身確實不兼容&#xff0c;不能像在 HTTP/1.1 中那樣用標準 WebSocket 協議&#xff08;ws:// / wss://&#xff09;進行升級握手。但這事兒細節比較多&#xff0c;下面詳細講講&#xff1a; ? HTTP/2 與 WebSocket 的關系 HTTP/2 不直接支持 WebSo…

LoRA 與 CoT 沖突嗎

對于一個具有CoT 能力的模型來說&#xff0c;采用普通的數據對其進行LoRA 微調可能會使原模型丟失CoT 能力&#xff0c;從而我們進行思考如下 CoT 與 LoRA 的“沖突”理解 目標不完全一致 導致的效果優化方向&#xff1a; CoT 側重于提高推理能力和可解釋性&#xff0c;它鼓勵…

Python爬蟲-爬取票牛明星演唱會數據,進行數據分析

前言 本文是該專欄的第61篇,后面會持續分享python爬蟲干貨知識,記得關注。 本文,筆者以“票牛”平臺為例。基于Python爬蟲,采集“票牛”平臺的明星演唱會(包含“演出城市,演出票價,演出時間”等等)的數據。 廢話不多說,具體實現思路和詳細邏輯,筆者將在正文結合完整…

uniapp的video遮蓋了popup

video的默認層級太高&#xff0c;導致popup彈出的時候&#xff0c;部分被video遮擋了 可以利用cover-view&#xff0c;將popup以及內部所有的標簽&#xff0c;全都換成cover-view&#xff0c;然后用一個變量控制其顯隱 比如原始&#xff1a; 現在&#xff1a;

java面試題02訪問修飾符有哪些?區別是什么?

訪問修飾符是面向對象編程中實現封裝的核心機制&#xff0c;用于控制類、屬性、方法等成員的可見性&#xff08;可訪問范圍&#xff09;。不同的訪問修飾符決定了其他類或代碼在何處可以訪問這些成員。 主要的訪問修飾符及其區別如下&#xff08;以 Java 和 C# 為代表&#xf…