文章目錄
- 請設計一個類,不能被拷貝
- 請設計一個類,只能在堆上創建對象
- 請設計一個類,只能在棧上創建對象
- 請設計一個類,不能被繼承
- 請設計一個類,只能創建一個對象(單例模式)
- 單例模式:
- 餓漢模式:
- 懶漢模式:
- 懶漢模式的線程安全問題
- 工廠模式
- 觀察者模式
請設計一個類,不能被拷貝
- 拷貝只會放生在兩個場景中:拷貝構造函數以及賦值運算符重載,因此想要讓一個類禁止拷貝, 只需讓該類不能調用拷貝構造函數以及賦值運算符重載即可。
C++98:
class CopyBan {// ...private: CopyBan(const CopyBan&); CopyBan& operator=(const CopyBan&);//...
};
原因:
- 設置成私有:如果只聲明沒有設置成private,用戶自己如果在類外定義了,就可以不 能禁止拷貝了
- 只聲明不定義:不定義是因為該函數根本不會調用,定義了其實也沒有什么意義,不寫 反而還簡單,而且如果定義了就不會防止成員函數內部拷貝了。
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&);// 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 NonInherit final
{
public:// ...
};
請設計一個類,只能創建一個對象(單例模式)
設計模式:
-
設計模式(Design Pattern)是一套被反復使用、多數人知曉的、經過分類的、代碼設計經驗的 總結。為什么會產生設計模式這樣的東西呢?就像人類歷史發展會產生兵法。最開始部落之間打 仗時都是人拼人的對砍。后來春秋戰國時期,七國之間經常打仗,就發現打仗也是有套路的,后 來孫子就總結出了《孫子兵法》。孫子兵法也是類似。
-
使用設計模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。 設計模 式使代碼編寫真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構一樣。
單例模式:
- 一個類只能創建一個對象,即單例模式,該模式可以保證系統中該類只有一個實例,并提供一個 訪問它的全局訪問點,該實例被所有程序模塊共享。比如在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,然后服務進程中的其他對象再 通過這個單例對象獲取這些配置信息,這種方式簡化了在復雜環境下的配置管理。
單例模式有兩種實現模式:
餓漢模式:
- 餓漢模式:就是說不管你將來用不用,程序啟動時就創建一個唯一的實例對象,提前(main函數啟動時)創建好實例對象
- 優點:實現簡單
- 缺點:1、可能會導致進程啟動慢、2、如果兩個單例有啟動先后順序,那么餓漢無法控制
class A
{
public:static A * GetInstance(){return &_inst;}void Add(const string & key, const string & value){_dict[key] = value;}void Print(){for (auto& kv : _dict){cout << kv.first << ":" << kv.second << endl;}cout << endl;}
private:// 構造函數私有A(){}A(const A & aa) = delete;A& operator=(const A& aa) = delete;map<string, string> _dict;int _n = 0;static A _inst;
};// 在程序入口之前就完成單例對象的初始化
A A::_inst;int main()
{// 無法創建//A aa1;//A aa2;// 可以進行調用A::GetInstance()->Add("sort", "排序");A::GetInstance()->Add("left", "左邊");A::GetInstance()->Add("right", "右邊");A::GetInstance()->Print();// 拷貝構造禁止使用// A copy(*A::GetInstance());// copy.Print();// A::GetInstance()->Add("left", "剩余");// copy.Print();// A::GetInstance()->Print();// 賦值也無法使用// *A::GetInstance() = *A::GetInstance();return 0;
}
- 如果這個單例對象在多線程高并發環境下頻繁使用,性能要求較高,那么顯然使用餓漢模式來避 免資源競爭,提高響應速度更好。
懶漢模式:
-
如果單例對象構造十分耗時或者占用很多資源,比如加載插件啊, 初始化網絡連接啊,讀取 文件啊等等,而有可能該對象程序運行時不會用到,那么也要在程序一開始就進行初始化, 就會導致程序啟動時非常的緩慢。 所以這種情況使用懶漢模式(延遲加載)更好。
- 優點:第一次使用實例對象時,創建對象。進程啟動無負載。多個單例實例啟動順序自由控制。
- 缺點:復雜
class B
{
public:static B* GetInstance(){if (_inst == nullptr){_inst = new B;}return _inst;}void Add(const string& key, const string& value){_dict[key] = value;}void Print(){for (auto& kv : _dict){cout << kv.first << ":" << kv.second << endl;}cout << endl;}static void DelInstance(){if (_inst){delete _inst;_inst = nullptr;}}private:B(){}~B(){// 持久化:要求把數據寫到文件cout << "數據寫到文件" << endl;}B(const B& aa) = delete;B& operator=(const B& aa) = delete;map<string, string> _dict;int _n = 0;static B* _inst;// 期望main函數結束后自動調用class gc{public:~gc(){DelInstance();}};static gc _gc;
};B* B::_inst = nullptr;// 期望main函數結束后自動調用
B::gc B::_gc;int main()
{B::GetInstance()->Add("sort", "排序");B::GetInstance()->Add("left", "左邊");B::GetInstance()->Add("right", "右邊");B::GetInstance()->Print();B::GetInstance()->Add("right", "xxx");B::GetInstance()->Print();// 顯示的調用釋放B::DelInstance();cout << "xxxxxxxxxxx" << endl;return 0;
}
懶漢模式的線程安全問題
- 懶漢模式的線程安全問題已經在 Linux多線程【線程互斥】 中講解:
- 大概實現如下:
- 局部的靜態對象,是在第一次調用時初始化
- C++11之前,他不是,也就說, C++11之前的編譯器,那么這個代碼不安全的
- C++11之后可以保證局部靜態對象的初始化是線程安全的,只初始化一次
class Singleton
{
public:// 提供獲取單例對象的接口函數static Singleton& GetInstance(){// 局部的靜態對象,是在第一次調用時初始化// C++11之前,他不是,也就說, C++11之前的編譯器,那么這個代碼不安全的// C++11之后可以保證局部靜態對象的初始化是線程安全的,只初始化一次static Singleton inst;return inst;}private:// 構造函數私有Singleton(){cout << "Singleton()" << endl;}// 防拷貝Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;
};int main()
{Singleton::GetInstance();return 0;
}
工廠模式
- 用于創建對象的一個接口,讓子類決定實例化哪個類
class Product {
public:virtual void show() = 0;
};class ConcreteProduct : public Product {
public:void show() override {std::cout << "ConcreteProduct Show" << std::endl;}
};class Factory {
public:virtual Product* createProduct() = 0;
};class ConcreteFactory : public Factory {
public:Product* createProduct() override {return new ConcreteProduct();}
};
觀察者模式
- 定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一主題對象
#include <iostream>
#include <list>
#include <string>class Observer {
public:virtual void update(std::string message) = 0;
};class ConcreteObserver : public Observer {
public:void update(std::string message) override {std::cout << "Received message: " << message << std::endl;}
};class Subject {
private:std::list<Observer*> observers;std::string message;public:void attach(Observer* observer) {observers.push_back(observer);}void notify() {for (auto observer : observers) {observer->update(message);}}void setMessage(std::string msg) {message = msg;}
};int main() {Subject subject;Observer* observer = new ConcreteObserver();subject.attach(observer);subject.setMessage("Hello, World!");subject.notify();delete observer;return 0;
}