【 C++ 】特殊類設計

1、請設計一個類,不能被拷貝

拷貝只會出現在兩個場景中:拷貝構造函數以及賦值運算符重載,因此想要讓一個類禁止拷貝,只需讓該類不能調用拷貝構造函數以及賦值運算符重載即可。在C++98和C++11都有相對應的方法來解決此問題,下面我們分別討論。

C++98:將拷貝構造函數與賦值運算符重載只聲明不定義,并且將其訪問權限設置為私有即可。示例如下:

class CopyBan
{
public://……
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;//刪除賦值運算符重載
};

庫里面如下都是經典的防拷貝:

  1. unique_ptr。
  2. thread線程 。
  3. mutex鎖 。
  4. istream 。
  5. ostream。

2、請設計一個類,不能被繼承

C++98:將該類的構造函數私有化即可,因為子類的構造函數被調用時,必須調用父類的構造函數初始化父類的那一部分成員,但無論是何種繼承方式,父類的私有成員在子類是不可見的,所以創建子類對象時子類就無法調用父類的構造函數對父類的成員初始化,繼而該類無法被繼承。

class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit()//私有化構造函數{}
};
  • C++98的這種方式其實不夠徹底,因為這個類仍然可以被繼承(編譯器不會報錯),只不過被繼承后無法實例化出對象而已。因此我們推出C++11的方法。

C++11:使用final關鍵字,final修飾類,表示該類不能被繼承。此時就算繼承后沒有創建對象也會編譯出錯。

class A final
{// ....
};

C++98是委婉的不能讓你繼承,C++11是直接的不能讓你繼承。

3、請設計一個類,只能在堆上創建對象

像我們平時創建對象,常見有如下三種在不同區域創建對象的方式:

class HeapOnly
{//……
};
int main()
{HeapOnly h1;static HeapOnly h2;HeapOnly* h3 = new HeapOnly;return 0;
}

既然只能在堆上創建對象,也就是只能通過new操作創建對象,有如下兩種方式。

法一:

  • 將類的構造函數私有,拷貝構造聲明成私有,防止別人調用拷貝在棧上生成對象。
  • 提供一個靜態的成員函數,在該靜態成員函數中完成對象的創建。
class HeapOnly
{
public://靜態成員函數完成對象的創建static HeapOnly* CreateObj(){return new HeapOnly;}
private://構造函數私有HeapOnly(){}//防拷貝//C++98,只聲明不實現,且聲明成私有HeapOnly(const HeapOnly&);//C++11HeapOnly(const HeapOnly&) = delete;
};
int main()
{HeapOnly* ph1 = HeapOnly::CreateObj();HeapOnly* ph2 = HeapOnly::CreateObj();delete ph1;delete ph2;//HeapOnly h1;//棧-錯誤//static HeapOnly h2;//靜態區-錯誤//HeapOnly copy(*ph2);//調用拷貝生成對象,在棧區,錯誤return 0;
}

注意:

  • 我們沒有必要對賦值運算符重載設置為私有&&只聲明不實現或者加上=delete(禁掉),因為賦值運算符重載是兩個已經存在的對象,既然已經存在,那勢必這倆對象就已經在堆區創建好了,所以它們之間進行賦值操作并不會出錯。除非你不想用,那你可以把賦值運算符重載給禁掉。
  • 而拷貝構造是拿一個已經存在的對象去構造一個對象,此對象是先前未存在的,且拷貝構造后是在棧上的,自然不符合題意,因此需要把拷貝構造給禁掉,而賦值運算符重載不需要禁掉。

法二:

  • 將析構函數私有化。
class HeapOnly
{
public:
private://析構函數私有化~HeapOnly(){}
};
int main()
{//HeapOnly ph1;err//static HeapOnly ph2;errHeapOnly* ph3 = new HeapOnly;return 0;
}

為何析構函數私有化就能確保只能在堆上創建對象呢?

  • C++是一個靜態綁定的語言。在編譯過程中,所有的非虛函數調用都必須分析完成。即使是虛函數,也需檢查可訪問性。因此, 當在棧上生成對象時,對象會自動調用析構函數釋放對象,也就說析構函數必須可以訪問 ,否則編譯出錯。而在堆上生成對象,由于析構函數由程序員調用(通過使用delete),所以不一定需要析構函數。

既然析構函數私有化,如何delete你new出的資源呢?

  • 因為delete操作會調用析構函數,而析構函數已經被置為私有了,那就無法調用,為了解決此問題,我們只需要在類的內部提供一個靜態成員函數,既然你類外不能調用私用成員,但是類里是可以調用的,因此我們在此成員函數中調用析構函數完成delete操作。
class HeapOnly
{
public://靜態成員函數釋放new的對象static void DelObj(HeapOnly* ptr){delete ptr;}
private://析構函數私有化~HeapOnly(){}
};
int main()
{HeapOnly* ph3 = new HeapOnly;//釋放ph3HeapOnly::DelObj(ph3);return 0;
}

當然,我也可以使用delete this來釋放new出的資源:

class HeapOnly
{
public:void DelObj(){delete this;}
private://析構函數私有化~HeapOnly(){}
};
int main()
{HeapOnly* ph3 = new HeapOnly;//釋放ph3ph3->DelObj();return 0;
}

delete this–對象請求自殺,執行后不能再訪問this指針。換句話說,你不能去檢查它、將它和其他指針比較、和 NULL比較、打印它、轉換它,以及其它的任何事情。不是很推薦這種方式。

4、請設計一個類,只能在棧上創建對象

法一:

  • 將構造函數設為私有,防止外部直接調用構造函數在堆上創建對象。
  • 提供靜態成員函數,內部調用私有的構造函數完成對象的創建。
class StackOnly
{
public://靜態成員函數,內部調用構造函數創建對象static StackOnly CreateObj(){return StackOnly();//傳值返回 —— 拷貝構造}
private://構造函數私有StackOnly(){}
};
int main()
{StackOnly h1 = StackOnly::CreateObj();//static StackOnly h2; 錯誤//StackOnly* h3 = new StackOnly; 錯誤return 0;
}

此法有一缺陷,無法避免外部調用拷貝構造函數在靜態區、堆區……創建對象。

int main()
{StackOnly h1 = StackOnly::CreateObj();//棧區static StackOnly h2(h1);//調用拷貝構造在靜態區創建對象StackOnly* h3 = new StackOnly(h1);//調用拷貝構造在堆區創建對象return 0;
}

法二:

  • 把構造函數設為公有。
  • 屏蔽operator new函數和operator delete函數。
class StackOnly
{
public:StackOnly(){}
private://C++98void* operator new(size_t size);void operator delete(void* p);//C++11//void* operator new(size_t size) = delete;//void operator delete(void* p) = delete;
};
int main()
{StackOnly h1;//StackOnly* h3 = new StackOnly(h1);不能使用new在堆區創建對象return 0;
}

new和delete默認調用的是全局的operator new函數和operator delete函數,但如果一個類重載了專屬的operator new函數和operator delete函數,那么new和delete就會調用這個專屬的函數。所以只要把operator new函數和operator delete函數屏蔽掉,那么就無法再使用new在堆上創建對象了。

上述做法雖然成功避免了在堆區創建對象,但是無法避免在靜態區或全局創建對象。

class StackOnly
{
private:void* operator new(size_t size) = delete;void operator delete(void* p) = delete;
};
StackOnly h1;//全局區
int main()
{static StackOnly h2;//靜態區return 0;
}

綜上,其實無論是法一還是法二多少都會存在點瑕疵,總是會有老六的出現,只能說是不那么嚴謹的情況下來看,法一算是ok的。

5、請設計一個類,只能創建一個對象(單例模式)

設計模式:

  • 設計模式(Design Pattern)是一套被反復使用、多數人知曉的、經過分類的、代碼設計經驗的總結。為什么會產生設計模式這樣的東西呢?就像人類歷史發展會產生兵法。最開始部落之間打仗時都是人拼人的對砍。后來春秋戰國時期,七國之間經常打仗,就發現打仗也是有套路的,后來孫子就總結出了《孫子兵法》。孫子兵法也是類似。
  • 使用設計模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。 設計模式使代碼編寫真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構一樣。

現在已經總結出了23種設計模式:

  • 創建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
  • 結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
  • 行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。

其中用的最多的就是單例模式。下面來展開討論。

單例模式:一個類只能創建一個對象,即單例模式,該模式可以保證系統中該類只有一個實例,并提供一個訪問它的全局訪問點,該實例被所有程序模塊共享。比如在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,然后服務進程中的其他對象再通過這個單例對象獲取這些配置信息,這種方式簡化了在復雜環境下的配置管理。

單例模式有兩種實現模式:餓漢模式和懶漢模式。下面展開討論。

餓漢模式

  • 餓漢模式就是不管你將來用不用,程序啟動時就創建一個唯一的實例對象。

實現方式如下:

  • 將構造函數私有化,并將拷貝構造和拷貝賦值設為私有或刪除,防止外部隨意創建對象或拷貝。
  • 在類里創建一個static靜態對象的指針,在進入程序入口之前就完成單例對象的初始化。
  • 提供一個static靜態成員函數,用來獲取單例對象的指針。
  • 將拷貝構造和拷貝賦值私有化,防止類外調用拷貝構造創建對象。
class Singleton
{
public://靜態成員函數內部獲取單例對象的指針static Singleton* GetInstance(){return _spInst;}void print();
private:Singleton(){}//C++98 防拷貝Singleton(const Singleton&);Singleton& operator=(Singleton const&);//C++11 防拷貝//Singleton(const Singleton&) = delete;//Singleton& operator=(Singleton const&) = delete;static Singleton* _spInst;//聲明int _a = 0;
};
Singleton* Singleton::_spInst = new Singleton;//定義,在程序入口之前就完成單例對象的初始化
void Singleton::print()
{cout << _a << endl;
}
int main()
{Singleton::GetInstance()->print();//Singleton st1;err//Singleton* st2 = new Singleton;err//Singleton copy(*Singleton::GetInstance());errreturn 0;
}

再比如我現在有一個信息管理的類,需要保證進程里只有一份這樣的信息,那么就需要把它設定為單例,整體框架和上面差不多其實,具體實現細節有所變動罷了:

//InfoMgr —— 單例
class InfoMgr
{
public://靜態成員函數獲取單例對象指針static InfoMgr* GetInstacne(){return _spInst;}//修改信息void SetAddress(const string& s){_address = s;}//獲取信息string& GetAddress(){return _address;}
private://構造函數私有化InfoMgr()	{}//刪除拷貝構造InfoMgr(const InfoMgr&) = delete;string _address;int _secretKey;static InfoMgr* _spInst;//聲明
};
InfoMgr* InfoMgr::_spInst = new InfoMgr;//定義
int main()
{//全局只有一個InfoMgr對象InfoMgr::GetInstacne()->SetAddress("江蘇省南京市");cout << InfoMgr::GetInstacne()->GetAddress() << endl;//江蘇省南京市return 0;
}

如果這個單例對象在多線程高并發環境下頻繁使用,性能要求較高,那么顯然使用餓漢模式來避免資源競爭,提高響應速度更好。

懶漢模式

如果單例對象構造十分耗時或者占用很多資源,比如加載插件啊, 初始化網絡連接啊,讀取文件啊等等,而有可能該對象程序運行時不會用到,那么也要在程序一開始就進行初始化,就會導致程序啟動時非常的緩慢。 所以這種情況使用懶漢模式(延遲加載)更好。

還是以上述信息管理的類為例,懶漢模式的實現方式如下:

  • 將構造函數置為私有,并將拷貝構造函數和賦值運算符重載函數設為私有或刪除,防止外部創建或拷貝對象。
  • 提供一個指向單例對象的static指針,并在程序入口之前先將其初始化為空。
  • 提供一個static靜態成員函數,只有當static指針為空時才初始化(也就是第一次調用此成員函數才創建對象),最后返回單例對象的指針。
  • 將拷貝構造和拷貝賦值私有化,防止類外調用拷貝構造創建對象。
//懶漢 -- 一開始不創建對象,第一次調用GetInstacne再創建對象
class InfoMgr
{
public://靜態成員函數獲取單例對象指針static InfoMgr* GetInstacne(){if (_spInst == nullptr){_spInst = new InfoMgr;}return _spInst;}//修改信息void SetAddress(const string& s){_address = s;}//獲取信息string& GetAddress(){return _address;}
private://構造函數私有化InfoMgr(){}//刪除拷貝構造InfoMgr(const InfoMgr&) = delete;string _address;int _secretKey;static InfoMgr* _spInst;//聲明
};
InfoMgr* InfoMgr::_spInst = nullptr;//定義
int main()
{//全局只有一個InfoMgr對象InfoMgr::GetInstacne()->SetAddress("江蘇省南京市");cout << InfoMgr::GetInstacne()->GetAddress() << endl;//江蘇省南京市return 0;
}

懶漢模式這樣寫是有問題的,還需要加鎖(雙檢查加鎖),餓漢模式不需要加鎖。

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

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

相關文章

ssm172旅行社管理系統的設計與實現

** &#x1f345;點贊收藏關注 → 私信領取本源代碼、數據庫&#x1f345; 本人在Java畢業設計領域有多年的經驗&#xff0c;陸續會更新更多優質的Java實戰項目希望你能有所收獲&#xff0c;少走一些彎路。&#x1f345;關注我不迷路&#x1f345;** 一 、設計說明 1.1 研究…

day03-Vue-Element

一、Ajax 1 Ajax 介紹 1.1 Ajax 概述 概念&#xff1a;Asynchronous JavaScript And XML&#xff0c;異步 的 JavaScript 和 XML。 作用&#xff1a; 數據交換&#xff1a;通過 Ajax 可以給服務器發送請求&#xff0c;并獲取服務器響應的數據。異步交互&#xff1a;可以在 不…

Java教程:SpringBoot項目如何對接Nacos實現服務發現治理,配置管理

–Nacos大家都知道&#xff0c;不懂的可以去官網或者網上查閱一下&#xff0c;本次給大家講解一下如何在SpringBoot項目中引入Nacos服務來進行服務治理與發現&#xff0c;配置管理等&#xff0c;在微服務當中是必不可少的&#xff0c;各個模塊之間可以通過Feign遠程調用&#x…

物聯網主機:為智能交通賦能

物聯網&#xff08;IoT&#xff09;技術的發展為智能交通領域帶來了許多創新的解決方案。而在物聯網應用中&#xff0c;物聯網主機起著關鍵的作用。本文將為大家介紹一款名為E6000的物聯網主機&#xff0c;它是一種多協議、多接口的物聯網主機&#xff0c;為智能交通系統的建設…

antvX6 - Vue自定義節點,并實現多種畫布操作,拖拽、縮放、連線、雙擊、檢索等等

一、 首先 antv x6 分為兩個版本 低版本和高版本 我這里是使用的2.0版本 并且搭配了相關插件 例如&#xff1a;畫布的圖形變換、地圖等 個人推薦 2.0版本&#xff0c;高版本配置多&#xff0c;可使用相關插件多&#xff0c;但是文檔描述小&#xff0c;仍在更新&#xff0c; 低…

小d和圖片壓縮

題目描述 小ddd和她對象小紅去海洋館玩了&#xff0c;但是由于小ddd拍照技術不好&#xff0c;他對象說把她拍的像嘎子&#xff01; 小ddd看了看&#xff0c;發現是小紅最近長痘痘了&#xff0c;于是他為了討小紅開心&#xff0c;讓痘痘看不見&#xff0c;自學了圖像壓縮這個技…

裝飾器模式 詳解 設計模式

裝飾器模式 它允許你在不改變對象結構的情況下&#xff0c;動態地將新功能附加到對象上。 結構&#xff1a; 抽象組件&#xff08;Component&#xff09;&#xff1a;定義了原始對象和裝飾器對象的公共接口或抽象類&#xff0c;可以是具體組件類的父類或接口。具體組件&…

固定排班計劃

目錄 1.按發車時間排序。 2.排班日期默認當天時間。 3.編輯不可修改線路和排班日期。 4.線路、車號、司機是否匹配&#xff0c;不匹配不可入庫&#xff08;和其他表比&#xff09;&#xff0c;線路、發車時間、司機、車號、日期、上下行相同不可入庫&#xff08;和自己表比…

GO語言學習筆記(與Java的比較學習)(一)

GO的優缺點&#xff1a; 此處引用華為云開發者聯盟的一篇文章&#xff1a; GO語言的亮點很明顯&#xff1a; GoDoc。 GoDoc的靜態語言分析能力很強大&#xff0c;可以直接從代碼和注釋生成漂亮的文檔。這一點區別于其他的類似工具如JavaDoc, PHPDoc或者JSDoc。這些工具需要添加…

如何在群暉Docker運行本地聊天機器人并結合內網穿透發布到公網訪問

文章目錄 1. 拉取相關的Docker鏡像2. 運行Ollama 鏡像3. 運行Chatbot Ollama鏡像4. 本地訪問5. 群暉安裝Cpolar6. 配置公網地址7. 公網訪問8. 固定公網地址 隨著ChatGPT 和open Sora 的熱度劇增,大語言模型時代,開啟了AI新篇章,大語言模型的應用非常廣泛&#xff0c;包括聊天機…

C# Socket通信從入門到精通(21)——TCP發送文件與接收文件 C#代碼實現

1、前言 我們在開發上位機軟件的過程中經常需要發送文件,本文就是介紹如何利用tcp客戶端發送文件、tcp服務器端接收文件,而且本文介紹的方法可以自動發送一個文件夾下的所有子目錄以及所有文件,經驗來自于實際項目,具備非常有價值的參考意義! 2、發送文件以及C#代碼 被發…

LeetCode第48天 買賣股票的最佳時機 買賣股票的最佳時機II 動態規劃

121. 買賣股票的最佳時機 class Solution { public:int maxProfit(vector<int>& prices) {// int res 0 ;// int low INT_MAX;// for (int i 0; i < prices.size(); i) {// low min(low, prices[i]);// res max(res, prices[i]-low);// }// return r…

低密度奇偶校驗碼LDPC(八)——QC-LDPC譯碼器FPGA設計概要

往期博文 低密度奇偶校驗碼LDPC&#xff08;一&#xff09;——概述_什么是gallager構造-CSDN博客 低密度奇偶校驗碼LDPC&#xff08;二&#xff09;——LDPC編碼方法-CSDN博客 低密度奇偶校驗碼LDPC&#xff08;三&#xff09;——QC-LDPC碼概述-CSDN博客 低密度奇偶校驗碼…

Linux系統--------內核參數調優、一鍵安裝nginx、tomcat調優

一、內核參數調優 默認的Linux內核參數考慮的是最通用場景&#xff0c;不符合用于支持高并發訪問的Web服務器的定義&#xff0c;根據業務特點來進行調整&#xff0c;當Nginx作為靜態web內容服務器、反向代理或者提供壓縮服務器的服務器時&#xff0c;內核參數的調整都是不同的…

Spring面試系列-02

1. Spring 中自動裝配有那些局限性? 自動裝配的局限性 重寫:仍需用<constructor-arg>和<property>配置來定義依賴,意味著總要重寫自動裝配。 基本數據類型:不能自動裝配簡單的屬性,例如基本數據類型、String字符串、和類。 模糊特性:自動裝配不如顯式裝配…

Vue點擊復制到剪切板

一、Vue2寫法 安裝 &#xff08;官網地址&#xff09; npm install --save vue-clipboard2 使用 //main.js import VueClipboard from vue-clipboard2 Vue.use(VueClipboard)//頁面使用 <button type"button"v-clipboard:copy"message"v-clipboard:su…

Mac電腦軟件開發的優缺點

Mac電腦軟件開發的優缺點 在軟件開發領域&#xff0c;Mac電腦一直以其獨特的優勢占有一席之地。然而&#xff0c;就像任何工具或平臺一樣&#xff0c;Mac電腦在軟件開發方面也存在其優點和缺點。本文將探討在Mac上進行軟件開發的利弊&#xff0c;幫助您了解是否應將Mac作為您的…

node.js 用 xml2js.Parser 讀 Freeplane.mm文件,生成測試用例.csv文件

Freeplane 是一款基于 Java 的開源軟件&#xff0c;繼承 Freemind 的思維導圖工具軟件&#xff0c;它擴展了知識管理功能&#xff0c;在 Freemind 上增加了一些額外的功能&#xff0c;比如數學公式、節點屬性面板等。 編寫 mm_xml2js_csv.js 如下 // 用 xml2js.Parser 讀 F…

Android 通過Intent打開第三方App

Android 使用 Intent 打開第三方應用或調用制定 Activity Intent intent new Intent(); intent.setClassName("package name", "activity name"); // 內部調用 intent.setComponent(new ComponentName("package name", "activity name&qu…

javaWebssh票據管理系統myeclipse開發mysql數據庫MVC模式java編程計算機網頁設計

一、源碼特點 java ssh票據管理系統是一套完善的web設計系統&#xff08;系統采用ssh框架進行設計開發&#xff09;&#xff0c;對理解JSP java編程開發語言有幫助&#xff0c;系統具有完整的源代碼和數據庫&#xff0c;系統主要采用B/S模 式開發。開發環境為TOMCAT7.0,My…