目錄
一、設計一個類,不能被拷貝
二、設計一個類,只能在堆上創建對象
三、設計一個類,只能在棧上創建對象
四、請設計一個類,不能被繼承
五、請設計一個類,只能創建一個對象(單例模式)
?C++🌷
一、設計一個類,不能被拷貝
拷貝只會發生在兩個場景中:拷貝構造函數以及賦值運算符重載;
因此 想要讓一個類禁止拷貝, 只需讓該類不能調用 拷貝構造函數 以及 賦值運算符重載 即可 。
- C++98
將拷貝構造函數與賦值運算符重載只聲明不定義,并且將其訪問權限設置為私有即可。
// C++98
class CopyBan
{private:Person(const CopyBan&);Person& 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;//...
};
二、設計一個類,只能在堆上創建對象
實現方式:
- 將類的構造函數私有,拷貝構造聲明成私有。防止別人調用拷貝在棧上生成對象。
- 提供一個靜態的成員函數,在該靜態成員函數中完成堆對象的創建。
class HeapOnly
{
public:static HeapOnly* CreateObject(){return new HeapOnly;}
private:HeapOnly() {}// C++98// 1.只聲明,不實現。因為實現可能會很麻煩,而你本身不需要// 2.聲明成私有HeapOnly(const HeapOnly&);// or// C++11 ? ?HeapOnly(const HeapOnly&) = delete;
};
三、設計一個類,只能在棧上創建對象
- 方法一:同上將構造函數私有化,然后設計靜態方法創建對象返回即可。
class StackOnly
{
public:static StackOnly CreateObj(){return StackOnly();}// 禁掉operator new可以把下面用new 調用拷貝構造申請對象給禁掉// StackOnly obj = StackOnly::CreateObj();// StackOnly* ptr3 = new StackOnly(obj);void* operator new(size_t size) = delete;void operator delete(void* p) = delete;
private:StackOnly():_a(0){}private:int _a;
};
四、請設計一個類,不能被繼承
- C++98方式
// C++98中構造函數私有化,派生類中調不到基類的構造函數。則無法繼承
class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};
- C++11方法
final 關鍵字, final? 修飾類,表示該類不能被繼承。
class A ?final
{// ....
};
五、請設計一個類,只能創建一個對象(單例模式)
設計模式:
設計模式( Design Pattern )是一套 被反復使用、多數人知曉的、經過分類的、代碼設計經驗的 總結 。為什么會產生設計模式這樣的東西呢?就像人類歷史發展會產生兵法。最開始部落之間打仗時都是人拼人的對砍。后來春秋戰國時期,七國之間經常打仗,就發現打仗也是有套路 的,后來孫子就總結出了《孫子兵法》。孫子兵法也是類似。使用設計模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。 設計模式使代碼編寫真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構一樣。
單例模式:
一個類只能創建一個對象,即單例模式,該模式可以保證系統中該類只有一個實例,并提供一個 訪問它的全局訪問點,該實例被所有程序模塊共享 。比如在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,然后服務進程中的其他對象再通過這個單例對象獲取這些配置信息,這種方式簡化了在復雜環境下的配置管理。
單例模式有兩種實現模式:
- 餓漢模式
就是說不管你將來用不用,程序啟動時就創建一個唯一的實例對象。
// 餓漢模式
// 優點:簡單
// 缺點:可能會導致進程啟動慢,且如果有多個單例類對象實例啟動順序不確定。
class Singleton
{
public:static Singleton* GetInstance(){return &m_instance;}
private:// 構造函數私有Singleton() {};// C++98 防拷貝Singleton(Singleton const&);Singleton& operator=(Singleton const&);// or// C++11Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;static Singleton m_instance;
};Singleton Singleton::m_instance; ?// 在程序入口之前就完成單例對象的初始化
如果這個單例對象在多線程高并發環境下頻繁使用,性能要求較高,那么顯然使用餓漢模式來避免資源競爭,提高響應速度更好。
- 懶漢模式
如果單例對象構造十分耗時或者占用很多資源,比如加載插件啊, 初始化網絡連接啊,讀取文件啊等等,而有可能該對象程序運行時不會用到,那么也要在程序一開始就進行初始化,就會導致程序啟動時非常的緩慢。 所以這種情況使用懶漢模式(延遲加載 )更好。
// 懶漢
// 優點:第一次使用實例對象時,創建對象。進程啟動無負載。多個單例實例啟動順序自由控制。
// 缺點:復雜
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;class Singleton
{
public:static Singleton* GetInstance() {// 注意這里一定要使用Double-Check的方式加鎖,才能保證效率和線程安全if (nullptr == m_pInstance) {m_mtx.lock();if (nullptr == m_pInstance) {m_pInstance = new Singleton();}m_mtx.unlock();}return m_pInstance;}// 實現一個內嵌垃圾回收類 ? ?class CGarbo {public:~CGarbo() {if (Singleton::m_pInstance)delete Singleton::m_pInstance;}};// 定義一個靜態成員變量,程序結束時,系統會自動調用它的析構函數從而釋放單例對象static CGarbo Garbo;
private:// 構造函數私有Singleton() {};// 防拷貝Singleton(Singleton const&);Singleton& operator=(Singleton const&);static Singleton* m_pInstance; // 單例對象指針static mutex m_mtx; ? //互斥鎖
};Singleton* Singleton::m_pInstance = nullptr;
Singleton::CGarbo Garbo;
mutex Singleton::m_mtx;int main()
{thread t1([] {cout << &Singleton::GetInstance() << endl; });thread t2([] {cout << &Singleton::GetInstance() << endl; });t1.join();t2.join();cout << &Singleton::GetInstance() << endl;cout << &Singleton::GetInstance() << endl;return 0;
}