網站首頁 編程語言 正文
設計一個類,只能在堆上創建對象
想要的效果實際是沒法直接在棧上創建對象。
首先cpp只要創建對象就要調用構造函數,因此先要把構造函數ban掉,把構造函數設計成private。但是單這樣自己也創建不了了。
因此提供一個創建的接口,只能調用該接口,該接口內部寫new
。而且要調用該接口需要先有對象指針調用,而要有對象先得調用構造函數實例化,因此必須設計成靜態函數
。
但是注意這樣還有拷貝函數可以調用HeapOnly copy(*p)
。此時生成的也是棧上的對象。因此要拷貝構造私有
,并且只聲明不實現(實現也是可以的,但是沒人用)。這種方式在c++98中叫防拷貝,比如互斥鎖。
#includeusing namespace std; class HeapOnly { private: HeapOnly() { } //C++98——防拷貝 HeapOnly(const HeapOnly&); public: static HeapOnly* CreateObj() { return new HeapOnly; } }; int main() { HeapOnly* p = HeapOnly::CreateObj(); return 0; }
對于防拷貝,C++11中有新的方式。函數=delete
。
#includeusing namespace std; class HeapOnly { private: HeapOnly() { } public: static HeapOnly* CreateObj() { return new HeapOnly; } //C++11——防拷貝 HeapOnly(const HeapOnly&) =delete; }; int main() { HeapOnly* p = HeapOnly::CreateObj(); return 0; }
總結:
1.將類的構造函數私有,拷貝構造聲明成私有。防止別人調用拷貝在棧上生成對象。
2.提供一個靜態的成員函數,在該靜態成員函數中完成堆對象的創建
設計一個類,只能在棧上創建對象
- 方法一:同上將構造函數私有化,然后設計靜態方法創建對象返回即可。
由于返回臨時對象,因此不能禁掉拷貝構造。
class StackOnly { public: static StackOnly CreateObject() { return StackOnly(); } private: StackOnly() {} };
- 方法二:調用類自己的專屬的operator new和operator delete,設置為私有。
因為new在底層調用void* operator new(size_t size)函數,只需將該函數屏蔽掉即可。注意:也要防止定位new。new先調用operator new申請空間,然后調用構造函數。delete先調用析構函數釋放對象所申請的空間,再調用operator delete釋放申請的對象空間。
class StackOnly { public: StackOnly() {} private: //C++98 void* operator new(size_t size); void operator delete(void* p); }; int main() { static StackOnly st;//缺陷,沒有禁掉靜態區的。 }
class StackOnly { public: StackOnly() {} //C++11 void* operator new(size_t size) = delete; void operator delete(void* p) = delete; }; int main() { static StackOnly st;//缺陷,沒有禁掉靜態區的。 }
設計一個類,不能被拷貝
拷貝只會放生在兩個場景中:拷貝構造函數以及賦值運算符重載,因此想要讓一個類禁止拷貝,只需讓該類不能調用拷貝構造函數以及賦值運算符重載即可。
- C++98將拷貝構造函數與賦值運算符重載只聲明不定義,并且將其訪問權限設置為私有即可。
class CopyBan { // ... private: CopyBan(const CopyBan&); CopyBan& operator=(const CopyBan&); //... };
原因:
1.設置成私有:如果只聲明沒有設置成private,用戶自己如果在類外定義了,就可以不能禁止拷貝了
2.只聲明不定義:不定義是因為該函數根本不會調用,定義了其實也沒有什么意義,不寫反而還簡單,而且如果定義了就不會防止成員函數內部拷貝了。
- C++11擴展delete的用法,delete除了釋放new申請的資源外,如果在默認成員函數后跟上=delete,表示讓編譯器刪除掉該默認成員函數。
class CopyBan { // ... CopyBan(const CopyBan&)=delete; CopyBan& operator=(const CopyBan&)=delete; //... };
設計一個類,不能繼承
C++98
// C++98中構造函數私有化,派生類中調不到基類的構造函數。則無法繼承 class NonInherit { public: static NonInherit GetInstance() { return NonInherit(); } private: NonInherit() {} }; class B : public NonInherit {}; int main() { //C++98中這個不能被繼承的方式不夠徹底,實際是可以繼承,限制的是子類繼承后不能實例化對象 B b; return 0; }
C++11為了更直觀,加入了final
關鍵字
class A final { }; class C: A {};
設計一個類,只能創建一個對象(單例模式)
之前接觸過了適配器模式和迭代器模式。
可以再看看工廠模式,觀察者模式等等常用一兩個的。
單例模式的概念
設計模式:設計模式(Design Pattern)是一套被反復使用、多數人知曉的、經過分類的、代碼設計經驗的總結。
為什么會產生設計模式這樣的東西呢?就像人類歷史發展會產生兵法。最開始部落之間打仗時都是人拼人的對砍。后來春秋戰國時期,七國之間經常打仗,就發現打仗也是有套路的,后來孫子就總結出了《孫子兵法》。孫子兵法也是類似。
使用設計模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。
設計模式使代碼編寫真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構一樣。
- 單例模式
一個類只能創建一個對象,即單例模式,該模式可以保證系統中該類只有一個實例,并提供一個訪問它的全局訪問點,該實例被所有程序模塊共享。比如在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,然后服務進程中的其他對象再通過這個單例對象獲取這些配置信息,這種方式簡化了在復雜環境下的配置管理。
1.如何保證全局(一個進程中)只有一個唯一的實例對象
參考只能在堆上創建對象和在棧上創建對象,禁止構造和拷貝構造及賦值。
提供一個GetInstance獲取單例對象。
2.如何提供只有一個實例呢?
餓漢模式和懶漢模式。
單例模式的實現
餓漢模式
餓漢模式
:程序開始main執行之前就創建單例對象,提供一個靜態指向單例對象的成員指針,初始時new一個對象給它。
class Singleton { public: static Singleton* GetInstance() { return _inst; } void Print() { cout<<"Print() "<<_val<Print(); }
懶漢模式
懶漢模式
:
懶漢模式出現的原因,單例類的構造函數中要做很多配置初始化工作,那么餓漢就不合適了,會導致程序啟動很慢。
linux是Posix的pthread庫,windows下有自己的線程庫。因此要使用條件編譯保證兼容性。因此c++11為了規范提供了語言級別的封裝(本質也是條件編譯,庫里實現了)。
關于保護第一次需要加鎖,后面都不需要加鎖的場景的可以使用雙檢查加鎖。
#include#ifdef _WIN32 //windos 提供多線程api #else //linux pthread #endif // class Singleton { public: static Singleton* GetInstance() { //保護第一次需要加鎖,后面都不需要加鎖的場景,可以使用雙檢查加鎖 //特點:第一次加鎖,后面不加鎖,保護線程安全,同時提高了效率 if( _inst == nullptr) { _mtx.lock(); if( _inst == nullptr ) { _inst = new Singleton; } _ntx.unlock(); } return _inst; } void Print() { cout<<"Print() "<<_val< Print(); }
餓漢模式和懶漢模式的對比
- 餓漢模式
- 優點:簡單
- 缺點:
- 如果單例對象構造函數工作比較多,會導致程序啟動慢,遲遲進不了入口main函數。
- 如果有多個單例對象,他們之間有初始化的依賴關系,餓漢模式也會有問題。比如有A和B兩個單例類,要求A單例先初始化,B必須在A之后初始化,那么餓漢無法保證。這種場景下用懶漢模式,懶漢可以先調用A::GetInstance(),再調用B::GetInstance()。
- 懶漢模式
- 優點:解決了餓漢的缺點,因為他是第一次調用GetInstance時創建初始化單例對象
- 缺點:相對餓漢復雜一點。
懶漢模式的優化
實現了”更懶“。
缺點:單例對象在靜態區,如果單例對象太大,不合適。再挑挑刺,這個靜態對象無法主動控制釋放。
#include#ifdef _WIN32 //windos 提供多線程api #else //linux pthread #endif // //其他版本懶漢 class Singleton { public: static Singleton* GetInstance() { static Singleton inst; return &inst; } void Print() { cout<<"Print() "<<_val< Print(); } #include #ifdef _WIN32 //windos 提供多線程api #else //linux pthread #endif // //其他版本懶漢 class Singleton { public: static Singleton* GetInstance() { static Singleton inst; return &inst; } void Print() { cout<<"Print() "<<_val< Print(); }
單例對象的釋放
單例對象一般不需要釋放。全局一直用的不delete也沒問題,進程如果正常銷毀,進程會釋放對應資源。
單例對象的直接釋放
#include#ifdef _WIN32 //windos 提供多線程api #else //linux pthread #endif // class Singleton { public: static Singleton* GetInstance() { //保護第一次需要加鎖,后面都不需要加鎖的場景,可以使用雙檢查加鎖 //特點:第一次加鎖,后面不加鎖,保護線程安全,同時提高了效率 if( _inst == nullptr) { _mtx.lock(); if( _inst == nullptr ) { _inst = new Singleton; } _ntx.unlock(); } return _inst; } static void DelInstance()/*調的很少,可以雙檢查也可以不雙檢查*/ { _mtx.lock(); if(!_inst) { delete _inst; _inst=nullptr; } _mtx.unlock(); } void Print() { cout<<"Print() "<<_val< Print(); }
內部垃圾回收類
上述場景其實還是可以擴展的。
假設析構函數有一些數據需要保存一下,持久化一下,不調用析構函數會存在問題,因此需要調用析構函數的時候處理。這就得保證main函數結束的時候保證調用析構(private)。
但是顯式調用DelInstance
可能會存在遺忘。
#include#ifdef _WIN32 //windos 提供多線程api #else //linux pthread #endif // class Singleton { public: static Singleton* GetInstance() { //保護第一次需要加鎖,后面都不需要加鎖的場景,可以使用雙檢查加鎖 //特點:第一次加鎖,后面不加鎖,保護線程安全,同時提高了效率 if( _inst == nullptr) { _mtx.lock(); if( _inst == nullptr ) { _inst = new Singleton; } _ntx.unlock(); } return _inst; } void Print() { cout<<"Print() "<<_val< Print(); }
總結
原文鏈接:https://blog.csdn.net/zstuyyyyccccbbbb/article/details/122751934
相關推薦
- 2023-06-04 React中的合成事件是什么原理_React
- 2022-06-07 使用Docker容器部署rocketmq單機的全過程_docker
- 2023-09-18 就同一個Service類中,一個事務方法調用另外一個有事務的方法
- 2023-07-15 es6中export和export default的區別
- 2022-04-21 C#?TrackBar拖動條改變滑塊顏色_C#教程
- 2023-02-02 C++示例講解觀察者設計模式_C 語言
- 2022-06-09 Nginx動靜分離配置實現與說明_nginx
- 2022-12-15 C++集體數據交換實現示例講解_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支