深入淺出設計模式——創建型模式之單例模式 Singleton

文章目錄

  • “天上天下,唯我獨尊”——單例模式
  • 單例模式簡介
  • 單例模式結構
  • 餓漢式
  • 懶漢式
  • 客戶端示例
    • 運行結果
  • 單例模式總結
  • 構建型模式 Creational Patterns 小結 Summary

代碼倉庫
在這里插入圖片描述

“天上天下,唯我獨尊”——單例模式

你能在電腦上調出兩個Windows任務管理器嗎?
假設能,如果兩個管理器顯示的數據相同,那何必要存在兩個呢?
如果兩個管理器顯示的數據不同,那我該相信哪一個呢?

試試看,應該有且僅有一個吧?一個系統里有且僅有一個Windows任務管理器實例供外界訪問 。如何保證系統里有且僅有一個實例對象呢?并且能夠供外界訪問?你可以在系統里定義一個統一的全局變量,但這并不能防止創建多個對象(想一想,為什么?)這就是單例模式的典型應用。

對于一個軟件系統中的某些類來說,只有一個實例很重要。假設Windows系統上可以同時調出兩個Windows任務管理器,這兩個任務管理器顯示的都是同樣的信息,那勢必造成內存資源的浪費;如果這兩個任務管理器顯示的是不同的信息,這也給用戶帶來了困惑,到底哪一個才是真實的狀態?

單例模式簡介

單例模式定義:
確保一個類只有一個實例,并提供一個全局訪問點來訪問這個唯一實例。

在這里插入圖片描述

單例模式結構

單例模式結構非常簡單,其UML圖如下所示,只包含一個類,即單例類。為防止創建多個對象,其構造函數必須是私有的(外界不能訪問)。另一方面,為了提供一個全局訪問點來訪問該唯一實例,單例類提供了一個公有方法getInstance來返回該實例。

在這里插入圖片描述

餓漢式

餓漢式:變量在聲明時便初始化。

// 餓漢式(立即加載)
// 餓漢式(Hungry Singleton):程序啟動時立即創建對象
class Singleton_Hungry {
public:static Singleton_Hungry* getInstance() {std::cout << "\n[Hungry] 獲取單例實例" << std::endl;static Singleton_Hungry instance; // 推薦方法,更安全更現代化return &instance;}void doSomething() const {std::cout << "\n[Hungry] 正在執行任務..." << std::endl;}private:// 禁止外界創建新的實例(私有構造、刪除拷貝構造和拷貝賦值)。Singleton_Hungry() {std::cout << "\n[Hungry] 構造函數調用" << std::endl;}~Singleton_Hungry() {std::cout << "\n[Hungry] 析構函數調用" << std::endl;}Singleton_Hungry(const Singleton_Hungry&) = delete;Singleton_Hungry& operator=(const Singleton_Hungry&) = delete;// static Singleton_Hungry* instance;
};// 靜態成員初始化 ??頭文件定義靜態變量,錯誤!
// 根本原因:違反了單一定義規則(One Definition Rule, ODR)
// 你在頭文件中定義了一個靜態變量,頭文件會被多個.cpp文件包含,這就導致在每個.cpp文件中都定義了一遍,最終會導致鏈接時的沖突,出現重復定義(multiple definition)錯誤。
// Singleton_Hungry* Singleton_Hungry::instance = new Singleton_Hungry();

可以看到,我們將構造方法定義為 private,這就保證了其他類無法實例化此類,必須通過 getInstance 方法才能獲取到唯一的 instance 實例,非常直觀。但餓漢式有一個弊端,那就是即使這個單例不需要使用,它也會在類加載之后立即創建出來,占用一塊內存,并增加類初始化時間。就好比一個電工在修理燈泡時,先把所有工具拿出來,不管是不是所有的工具都用得上。就像一個饑不擇食的餓漢,所以稱之為餓漢式。

懶漢式

懶漢式:先聲明一個空變量,需要用時才初始化。例如:

我們先聲明了一個 instance 變量,當需要使用時判斷此變量是否已被初始化,沒有初始化的話才 new 一個實例出來。就好比電工在修理燈泡時,開始比較偷懶,什么工具都不拿,當發現需要使用螺絲刀時,才把螺絲刀拿出來。當需要用鉗子時,再把鉗子拿出來。就像一個不到萬不得已不會行動的懶漢,所以稱之為懶漢式

懶漢式解決了餓漢式的弊端,好處是按需加載,避免了內存浪費,減少了類初始化時間。

Singleton.h

// 確保一個類只有一個實例。
// 提供全局訪問點,讓用戶方便訪問// 懶漢式(線程安全,推薦寫法)
// 懶漢式(Lazy Singleton):延遲創建對象(用時才創建)
class Singleton_Lazy {
public:// 第一次 調用時,執行內部的 lambda 表達式,創建一個新的單例對象。// 后續所有次 調用時,都直接跳過這個 lambda,不再執行創建對象的操作。static Singleton_Lazy* getInstance() {// template< class Callable, class... Args >// void call_once(std::once_flag& flag, Callable&& f, Args&&... args);// 確保給定的**可調用對象(如lambda表達式)**僅被調用一次。// 如果多個線程同時執行該行代碼,也能確保只有一個線程實際執行了初始化操作,其他線程則會等待,直到第一次調用完成后再繼續執行,且不會重復執行初始化操作。// flag:一個標志位,標識對應的初始化是否已經完成。// Callable:一個可調用對象(比如lambda表達式或函數),代表真正要執行的初始化動作。std::call_once(initFlag, []() {std::cout << "\n[Lazy] 創建新的實例" << std::endl;// instance = std::make_unique<Singleton_Lazy>(); // ? 更安全,更推薦instance.reset(new Singleton_Lazy());});std::cout << "\n[Lazy] 獲取單例實例" << std::endl;return instance.get();}void doSomething() const {std::cout << "\n[Lazy] 正在執行任務..." << std::endl;}private:Singleton_Lazy() {std::cout << "\n[Lazy] 構造函數調用" << std::endl;}~Singleton_Lazy() {std::cout << "\n[Lazy] 析構函數調用" << std::endl;}// 拷貝構造函數(Copy Constructor)// 當使用一個已存在對象初始化另一個新對象時,調用拷貝構造函數Singleton_Lazy(const Singleton_Lazy&) = delete;// 拷貝賦值運算符(Copy Assignment Operator)// 當使用一個已存在的對象去給另一個已存在的對象賦值時調用。Singleton_Lazy& operator=(const Singleton_Lazy&) = delete;// ??一定要保留這兩個聲明!static std::unique_ptr<Singleton_Lazy> instance;static std::once_flag initFlag;// 🔑加上這一句:// 允許 std::unique_ptr 調用私有析構函數friend class std::default_delete<Singleton_Lazy>;
};

Singleton.cpp

#include "Singleton.h"std::unique_ptr<Singleton_Lazy> Singleton_Lazy::instance = nullptr;
std::once_flag Singleton_Lazy::initFlag;

客戶端示例

#define THREAD_NUM 6
#include <iostream>
#include <pthread.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "Singleton.h"pid_t GetThreadId() {// syscall 是一個系統調用接口,可以讓你直接調用操作系統提供的底層功能。// SYS_gettid 是 Linux 系統調用號,表示獲取當前線程的線程ID(gettid)。// syscall(SYS_gettid) 實際上是執行 gettid() 系統調用的操作,返回當前線程的線程ID。// 該調用返回當前線程的線程ID,通常與 pthread_self() 的返回值相同,但是 gettid 是返回內核級線程ID,而 pthread_self() 返回的是 POSIX 線程庫級別的線程ID// SYS_gettid 是一個常量,表示獲取當前線程ID的系統調用號。// 每個系統調用都有一個唯一的編號(常量),用于標識該系統調用。SYS_gettid 對應的是獲取線程ID的操作。return syscall(SYS_gettid);
}void* callSingleton_Lazy(void* arg) {int threadID = *(int*)arg;Singleton_Lazy *s = Singleton_Lazy::getInstance();printf("[Lazy] 線程編號: %d, 實例地址: %d\n", threadID, GetThreadId());// printf("[Hungry] 線程編號: %d, 實例地址: %p\n", threadID, s);return 0;
}void* callSingleton_Hungry(void* arg) {// 將arg 從 void* 類型的通用指針強制轉換成 int*類型的指針, 然后對轉換后的指針解引用,取出實際的整型數值(即線程編號)。int threadID = *(int*)arg;Singleton_Hungry *s = Singleton_Hungry::getInstance();printf("[Hungry] 線程編號: %d, 實例地址: %d\n", threadID, GetThreadId());// printf("[Hungry] 線程編號: %d, 實例地址: %p\n", threadID, s);return 0;
}int main() {pthread_t threads_pool[THREAD_NUM];int tids[THREAD_NUM], params[THREAD_NUM];for(int i = 0; i < THREAD_NUM; i++) {params[i] = i; // 獨立參數,避免競爭/*int pthread_create(pthread_t *restrict thread,const pthread_attr_t *restrict attr,void *(*start_routine)(void *),void *restrict arg);*/// 前半部分線程調用懶漢式單例if(i < THREAD_NUM / 2)tids[i] = pthread_create(&threads_pool[i], NULL, callSingleton_Lazy, (void*)&params[i]);else // 后半部分線程調用餓漢式單例tids[i] = pthread_create(&threads_pool[i], NULL, callSingleton_Hungry, (void*)&params[i]);// On success, pthread_create() returns 0; on error, it returns an error number, and the contents of *thread are undefined.if(tids[i]) {printf("Error: unable to create thread.\n");exit(-1);}}for(int i = 0; i < THREAD_NUM; i++) {// On success, pthread_join() returns 0; on error, it returns an error numbertids[i] = pthread_join(threads_pool[i], NULL);if(tids[i]) {printf("Error: unable to join thread.\n");exit(-1);}}printf("main exiting.\n");return 0;
}

運行結果

在這里插入圖片描述

單例模式總結

單例模式讓一個類同時負責了『業務功能』和『自身的創建與生命周期管理』兩個職責。
在這里插入圖片描述
在這里插入圖片描述

構建型模式 Creational Patterns 小結 Summary

在這里插入圖片描述

之后我會持續更新,如果喜歡我的文章,請記得一鍵三連哦,點贊關注收藏,你的每一個贊每一份關注每一次收藏都將是我前進路上的無限動力 !!!↖(▔▽▔)↗感謝支持!

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

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

相關文章

靜電釋放檢測漏報率↓85%!陌訊多模態融合算法在電子廠ESD防護實戰解析

?摘要?? 基于邊緣計算的靜電釋放(ESD)視覺檢測方案&#xff0c;通過多模態融合技術顯著提升復雜場景魯棒性。實測顯示&#xff1a;在電子元件裝配線上&#xff0c;ESD事件檢測mAP0.5達89.1%&#xff0c;較基線模型提升28.3%。一、行業痛點&#xff1a;ESD檢測的隱形危機根據…

RAL-2025 | “藏寶圖”驅動的具身導航!HAM-Nav:基于手繪地圖引導的機器人導航

作者&#xff1a;Aaron Hao Tan, Angus Fung, Haitong Wang, Goldie Nejat單位&#xff1a;多倫多大學機械與工業工程系論文標題&#xff1a;Mobile Robot Navigation Using Hand-Drawn Maps: A Vision Language Model Approach出版信息&#xff1a;IEEE ROBOTICS ANDAUTOMATI…

Vue.js 與后端技術結合開發指南

Vue.js 作為現代化的前端框架&#xff0c;可以與多種后端技術完美結合&#xff0c;構建全棧應用。下面我將詳細介紹 Vue 可以與哪些后端技術結合開發&#xff0c;并提供可視化示例。Vue 可結合的后端技術概覽主流組合方案對比后端技術適合場景優點缺點學習曲線Node.js全棧JavaS…

邏輯回歸在銀行貸款審批中的應用:參數選擇與實踐

目錄 一、數據背景與預處理 1.數據前五行 2.數據預處理步驟 二、邏輯回歸的正則化參數選擇 1.交叉驗證選擇最優C 2.為什么選擇召回率作為評估指標&#xff1f; 三、參數選擇的核心結論 四、后續優化方向 在銀行貸款審批場景中&#xff0c;準確判斷貸款人是否符合貸款條…

數據結構前篇 - 深入解析數據結構之復雜度

目錄一、數據結構前言1.1 數據結構1.2 算法二、算法效率2.1 復雜度的概念三、時間復雜度3.1 大O的漸進表示法3.2 時間復雜度計算示例3.2.1 示例13.2.2 示例23.2.3 示例33.2.4 示例43.2.5 示例53.2.6 示例63.2.7 示例7四、空間復雜度4.1 空間復雜度計算示例4.1.1 示例14.1.2 示例…

Master Prompt:AI時代的萬能協作引擎

1. Master Prompt&#xff1a;為什么它正在重塑AI協作范式大模型落地的最大痛點不是技術本身&#xff0c;而是人機協作的斷裂。當企業采購了昂貴的AI系統&#xff0c;卻發現輸出內容反復偏離預期&#xff0c;團隊成員抱怨“AI總聽不懂我要什么”&#xff0c;這種場景每天在無數…

《Kubernetes部署篇:基于Kylin V10+ARM架構CPU使用containerd部署K8S 1.33.3容器板集群(一主多從)》

總結:整理不易,如果對你有幫助,可否點贊關注一下? 更多詳細內容請參考:企業級K8s集群運維實戰 一、架構圖 如下圖所示: 二、環境信息 基于x86_64+aarch64架構使用containerd部署K8S 1.33.3集群資源合集(一主多從) 2、部署規劃 主機名 K8S版本 系統版本 CPU架構 內核版…

一次性接收大量上傳圖片,后端優化方式

文章目錄1. 分塊接收與流式處理2. 異步處理3. 內存映射與臨時文件4. 數據庫優化5. 緩存策略6. 壓縮與格式優化7. 限流與并發控制8. 分布式存儲9. 響應優化10. 監控與錯誤處理11. 數據庫連接池優化1. 分塊接收與流式處理 使用流式處理避免將所有圖片加載到內存中&#xff1a; …

二分查找(基礎)

競賽中心 - 藍橋云課 #include <iostream> #include<bits/stdc.h> using namespace std; #define int long long int N; struct NO {int A,B; }a[10001]; bool ok(int V) {for (int i 0; i < N; i){if (a[i].A / V ! a[i].B){return false;}}return true; } …

流式編程學習思路

流式編程學習思路 作為Java初級工程師,想要掌握流式編程并向高級工程師進階,需要從基礎到進階逐步掌握,結合實戰場景深化理解。以下是為你量身定制的學習清單和思路: 一、基礎階段:吃透 Java Stream 核心API 1. 掌握 Stream 的基本概念 什么是 Stream:理解它與集合(Co…

13-14linux三劍客grep,sed,awk

目錄 三劍客支持擴展正則寫法 grep命令 sed命令 sed指定行查找&#xff1a; sed模糊過濾文件內容 sed之刪除&#xff1a; sed之替換&#xff1a; sed追加插入替換&#xff1a; sed后向引用&#xff1a; awk命令 awk按照行查找 awk模糊過濾文件內容 awk取列 awk指…

損失函數和調度器相關類代碼回顧理解 |nn.CrossEntropyLoss\CosineAnnealingLR

目錄 nn.CrossEntropyLoss CosineAnnealingLR nn.CrossEntropyLoss loss_func nn.CrossEntropyLoss(reduction"sum") 定義nn.CrossEntropyLoss交叉熵損失函數&#xff0c;reduction參數設置為"sum"&#xff0c;表示將所有樣本的損失相加。reduction 參…

中國不同類型竹林分布數據

中國竹林分布的主要特點簡介&#xff1a;總體分布格局&#xff1a;核心區域&#xff1a; 主要分布在長江流域及以南的廣大亞熱帶和熱帶地區。北界&#xff1a; 大致以黃河流域為北界&#xff0c;但天然成片竹林在秦嶺-淮河一線以南才比較普遍。人工引種或特殊小環境下&#xff…

Sqlserver備份恢復指南-完整備份恢復

博主會用簡單清晰的方式&#xff0c;帶你系統學習使用T-SQL命令行的方式 給SQL Server 做備份與恢復。我們按照從零開始、逐步深入的路線來講解&#xff01; 完整備份恢復-差異增量備份恢復-事務日志備份恢復 &#x1f538; SQL Server 備份類型&#xff1a;類型說明完整備份&a…

AI 調酒師上崗!接管酒吧吧臺

7月29日&#xff0c;馬老師的 HHB 音樂酒吧在阿里巴巴西溪園區正式開業&#xff0c;開業這天迎來了一位神秘嘉賓“AI 調酒師”&#xff01; 這位 AI 調酒師不僅能根據你的MBTI、今日情緒、星座運勢、江湖花名等為你特調一杯雞尾酒&#xff0c;還能為這杯酒配上故事和詩文。 點…

【C++進階】一文吃透靜態綁定、動態綁定與多態底層機制(含虛函數、vptr、thunk、RTTI)

【C進階】一文吃透靜態綁定、動態綁定與多態底層機制&#xff08;含虛函數、vptr、thunk、RTTI&#xff09;作者&#xff1a;你的C教練 日期&#xff1a;2025-08-01目錄 靜態綁定 vs 動態綁定非虛函數的三大坑多態的四要素虛析構函數為什么必須寫&#xff1f;探秘 vptr/vftable…

VUE基礎知識2

1.計算屬性&#xff1a;使用計算屬性來描述依賴響應式狀態的復雜邏輯。關鍵字computed:{}//計算屬性&#xff0c;使用的時候和函數方法不一樣&#xff0c;不需要加括號。簡單來說就是模板方法的復雜邏輯放到了計算屬性中去。2.計算屬性緩存VS方法&#xff1a;計算屬性值會基于其…

在PyCharm中將現有Gitee項目重新上傳為全新項目

如果你想將當前本地的Gitee項目重新上傳為一個全新的Gitee項目&#xff08;保留本地代碼但斷開與原倉庫的關聯&#xff09;&#xff0c;可以按照以下步驟操作&#xff1a; 刪除舊的Git遠程倉庫關聯 打開PyCharm&#xff0c;進入你的項目 點擊頂部菜單 Git > Manage Remotes …

設計模式1:創建型模式

設計模式1&#xff1a;創建型模式 設計模式2&#xff1a;結構型模式&#xff08;編寫中&#xff09; 設計模式3&#xff1a;行為型模式&#xff08;編寫中&#xff09; 前言 設計模式是軟件開發中經過驗證的可復用解決方案&#xff0c;它們源自實踐、提煉于經驗&#xff0c;并…

React--》規劃React組件庫編碼規范與標準 — Button篇

目前前端組件化已經成為前端開發的核心思想之一&#xff0c;在這篇文章中將深入探討如何規劃一個規范的Button組件&#xff0c;讓它不僅能高效支持不同的功能需求還能確保跨項目、跨團隊的一致性&#xff0c;拋磚引玉的方式引出后面組件庫的其他組件的開發&#xff01; 目錄 B…