Effective C++(2)

文章目錄

  • 2. 構造、析構、賦值運算
    • 條款05:了解C++默默編寫并調用哪些函數
    • 條款06:若不想使用編譯器自動生成的函數,就該明確拒絕
    • 條款07:為多態基類聲明virtual析構函數
    • 條款08:別讓異常逃離析構函數
    • 條款09:絕不在構造和析構過程中調用virtual函數
    • 條款10:令 operator= 返回一個指向 *this 的引用
    • 條款11:在operator=中處理“自我賦值”
    • 條款12:復制對象時勿忘其每一個成分


2. 構造、析構、賦值運算

條款05:了解C++默默編寫并調用哪些函數

如果你自己沒有聲明,編譯器會為他聲明一個copy構造函數、一個copy assignment操作符和一個析構函數。
如果你沒有聲明任何構造函數,編譯器也會聲明一個default構造函數。

class Empyt{};等價于class Empty{
public:Empty(){...}   //defalut構造函數Empty(const Empty& rhs){...}  //copy構造函數~Empty(){...}   //析構函數Empty& operator=(const Empty& rhs){...}  //copy assignment操作符
};唯有當這些函數被調用,它們才會被編譯器創建出來。
Empty e1;                   // 默認構造函數 & 析構函數
Empty e2(e1);               // 拷貝構造函數
e2 = e1;                    // 拷貝賦值運算符

條款06:若不想使用編譯器自動生成的函數,就該明確拒絕

原書中使用的做法是將不想使用的函數聲明為private,但在 C++11 后我們有了更好的做法

class Uncopyable {
public:Uncopyable(const Uncopyable&) = delete;Uncopyable& operator=(const Uncopyable&) = delete;
};

條款07:為多態基類聲明virtual析構函數

當派生類對象經由一個基類指針被刪除,而該基類指針帶著一個非虛析構函數,其結果是未定義的,可能會無法完全銷毀派生類的成員,造成內存泄漏。消除這個問題的方法就是對基類使用虛析構函數:

class Base {
public:Base();virtual ~Base();
};

如果你不想讓一個類成為基類,那么在類中聲明虛函數是是一個壞主意,因為額外存儲的虛表指針會使類的體積變大。

只要基類的析構函數是虛函數,那么派生類的析構函數不論是否用virtual關鍵字聲明,都自動成為虛析構函數。
虛析構函數的運作方式是,最深層派生的那個類的析構函數最先被調用,然后是其上的基類的析構函數被依次調用。

欲實現出virtual函數,對象必須攜帶某些信息,主要用來在運行期決定哪一個virtual函數該被調用。這份信息通常是由一個所謂vptr(virtual table pointer)指針支出。vptr指向一個由函數指針構成的數組,稱為vtbl(virtual table);每一個帶有virtual函數的class都有一個相應的vtbl。當對象調用某一virtual函數,實際被調用的函數取決于該對象的vptr所指的那個vtbl——編譯器在其中尋找適當的函數指針。

如果你想將基類作為抽象類使用——也就是不能被實體化的類,但手頭上又沒有別的虛函數,那么將它的析構函數設為純虛函數是一個不錯的想法。

class Base {
public:virtual ~Base() = 0 {}
};
  • 帶有多態性質的base classes應該聲明一個virtual析構函數。如果class帶有任何virtual函數,它就應該擁有一個virtual析構函數
  • 類的設計目的如果不是作為base classes使用,或不是為了具備多態性,就不該聲明virtual析構函數

條款08:別讓異常逃離析構函數

在析構函數中吐出異常并不被禁止,但為了程序的可靠性,應當極力避免這種行為。

為了實現 RAII,我們通常會將對象的銷毀方法封裝在析構函數中,如下例子:

class DBConn {
public:...~DBConn() {db.close();    // 該函數可能會拋出異常}private:DBConnection db;
};

但這樣我們就需要在析構函數中完成對異常的處理,以下是幾種常見的做法:

  • 殺死程序
DBConn::~DBConn() {try { db.close(); }catch (...) {// 記錄運行日志,以便調試std::abort();}
}
  • 吞下因調用close而發生的有異常。不推薦,因為它壓制了“某些動作失敗”的重要信息。
DBConn::~DBConn() {try { db.close(); }catch (...) {制作運轉記錄,記下對close的調用失敗}
}
  • 重新設計接口,使其客戶有機會對可能出現的問題做出反應
class DBConn {
public:...void close() {db.close();closed = true;}~DBConn() {if (!closed) {try {db.close();}catch(...) {// 處理異常}}}private:DBConnection db;bool closed;
};
  • 析構函數絕對不要吐出異常。如果一個被析構函數調用的函數可能拋出異常,析構函數應該捕捉任何異常,然后吞下他們或結束程序。
  • 如果客戶需要對某個操作函數運行期間拋出的異常做出反應,那么class應該提供一個普通函數執行該從左。

條款09:絕不在構造和析構過程中調用virtual函數

在創建派生類對象時,基類的構造函數永遠會早于派生類的構造函數被調用,而基類的析構函數永遠會晚于派生類的析構函數被調用。

在派生類對象的基類構造和析構期間,對象的類型是基類而非派生類,因此此時調用虛函數會被編譯器解析至基類的虛函數版本,通常不會得到我們想要的結果。

間接調用虛函數是一個比較難以發現的危險行為,需要盡量避免:

class Transaction {
public:Transaction() { Init(); }virtual void LogTransaction() const = 0;private:void Init(){...LogTransaction();      // 此處間接調用了虛函數!}
};

如果想要基類在構造時就得知派生類的構造信息,推薦的做法是在派生類的構造函數中將必要的信息向上傳遞給基類的構造函數:

class Transaction {
public:explicit Transaction(const std::string& logInfo);void LogTransaction(const std::string& logInfo) const;...
};Transaction::Transaction(const std::string& logInfo) {LogTransaction(logInfo);                           // 更改為了非虛函數調用
}class BuyTransaction : public Transaction {
public:BuyTransaction(...): Transaction(CreateLogString(...)) { ... }    // 將信息傳遞給基類構造函數...private:static std::string CreateLogString(...);
}
  • 在構造和析構期間不要調用virtual函數,因為這類調用從不下降至derived class 。

條款10:令 operator= 返回一個指向 *this 的引用

class Widget {
public:Widget& operator+=(const Widget& rhs) {    // 這個條款適用于...                                    // +=, -=, *= 等等運算符return *this;}Widget& operator=(int rhs) {               // 即使參數類型不是 Widget& 也適用...return *this;}
};

條款11:在operator=中處理“自我賦值”

“自我賦值”發成在對象賦值給自己時

class Widget{};
Widget w;
w = w;  //賦值給自己a[i] = a[j]; //可能發生自我賦值
*px = *py; //可能發生自我賦值

一些情況下可能會導致意外的錯誤,例如在復制堆上的資源時:

//若rhs和*this指向的是相同的對象,就會導致訪問到已刪除的數據。
Widget& operator=(const Widget& rhs){delete pb;pb = new Bitmap(*rhs.pb);return *this;
}

最簡單的解決方法是在執行后續語句前先進行證同測試(Identity test):

Widget& operator=(const Widget& rhs){if(this == rhs) return *this;  //證同測試delete pb;pb = new Bitmap(*rhs.pb);return *this;
}

適當安排語句的順序,就可以做到使整個過程具有異常安全性。例如以下代碼,只需要注意在復制pb所指東西之前別刪除pb:

Widget& operator=(const Widget& rhs){Bitmap* pOrig = pb;pb = new Bitmap(*rhs.pb);delete pOrig; return *this;
}

還有一種取巧的做法是使用 copy and swap 技術,這種技術聰明地利用了棧空間會自動釋放的特性,這樣就可以通過析構函數來實現資源的釋放:

Widget& operator=(const Widget& rhs) {Widget temp(rhs);std::swap(*this, temp);return *this;
}
  • 確保當前對象自我賦值時operator=有良好的行為。其中技術包括比較“來源對象”和“目標對象”的地址、進行周到的語句順序、以及copy-and-swap
  • 確定任何函數如果操作一個以上的對象,而其中多個對象是同一個對象時,其行為仍然正確。

條款12:復制對象時勿忘其每一個成分

這個條款正如其字面意思,當你決定手動實現拷貝構造函數或拷貝賦值運算符時,忘記復制任何一個成員都可能會導致意外的錯誤。

當使用繼承時,繼承自基類的成員往往容易忘記在派生類中完成復制,如果你的基類擁有拷貝構造函數和拷貝賦值運算符,應該記得調用它們:

class PriorityCustomer : public Customer {
public:PriorityCustomer(const PriorityCustomer& rhs);PriorityCustomer& operator=(const PriorityCustomer& rhs);...private:int priority;
}PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs): Customer(rhs),                // 調用基類的拷貝構造函數priority(rhs.priority) {...
}PriorityCustomer::PriorityCustomer& operator=(const PriorityCustomer& rhs) {Customer::operator=(rhs);       // 調用基類的拷貝賦值運算符priority = rhs.priority;return *this;
}

不要嘗試在拷貝構造函數中調用拷貝賦值運算符,或在拷貝賦值運算符的實現中調用拷貝構造函數。

拷貝構造函數調用拷貝賦值運算符:對正在構造中的對象執行賦值意味著對尚未初始化的對象 執行某些只對初始化的對象有意義的操作。
拷貝賦值運算符調用拷貝構造函數:將試圖構造一個已經存在的對象。

  • 拷貝構造函數應該確保復制對象內的所有成員變量及所有base class成分
  • 不要嘗試以某個拷貝函數實現另一個。應該將共同機能放進第三個函數中,并由兩個拷貝函數共同調用。

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

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

相關文章

微信小程序報錯:notifyBLECharacteristicValueChange:fail:nodescriptor的解決辦法

文章目錄 一、發現問題二、分析問題二、解決問題 一、發現問題 微信小程序報錯:notifyBLECharacteristicValueChange:fail:nodescriptor 二、分析問題 這個提示有點問題,應該是該Characteristic的Descriptor有問題,而不能說nodescriptor。 …

web前端之解決img元素組件自有高度的問題

MENU 前言解決辦法vertical-align 前言 在HTML和CSS中,img元素默認是行內元素(inline element),類似于文本。由于文本有基線(baseline),所以即使是空白的img元素也會占據一定的高度,以便使基線對齊。 解決辦法 要解決這個問題&…

axios如何傳遞數組作為參數,后端又如何接收呢????

前端的參數是一個數組。 前端編寫: 后端接收:

Iterater迭代器和增強for循環

1、Collection接口遍歷元素—Iterator迭代器 看一下下面這張圖片:可以看出Collection接口有一個父接口Iterable,Iterable接口有一個iterator()方法,iterator()方法的類型是Iterator迭代器,實際上當我們使用方法時,返回…

Go語言的pprof工具是如何使用的?

文章目錄 Go語言的pprof工具詳解pprof的使用runtime/pprofnet/http/pprof 快速開始獲取采樣數據通過pprof工具進行性能分析總結 Go語言的pprof工具詳解 Go語言作為一個高性能、高并發的編程語言,對性能優化有著極高的要求。在Go語言的標準庫中,pprof是一…

linux 安全 iptables防火墻 (一)

Linux包過濾防火墻概述 Linux 系統的防火墻 :IP信息包過濾系統,它實際上由兩個組件netfilter 和 iptables組成。 主要工作在網絡層,針對IP數據包。體現在對包內的IP地址、端口、協議等信息的處理上。 兩大組件 netfilter內核組件 iptables應…

blender安裝cats-blender-plugin-0-19-0插件,導入pmx三維模型

UE5系列文章目錄 文章目錄 UE5系列文章目錄前言一、Blender安裝二、cats-blender-plugin-0-19-0插件下載三、下載bmp文件四、在blender2.93中安裝cats-blender-plugin-0-19-0插件 前言 blender本身不支持pmx三維模型,需要用到cats-blender-plugin-0-19-0插件。 一…

構建全面的無障礙學習環境:科技之光,照亮學習之旅

在信息與科技日益發展的當下,為所有人群提供一個包容和平等的學習環境顯得尤為重要,特別是對于盲人朋友而言,無障礙學習環境的構建成為了一項亟待關注與深化的課題。一款名為“蝙蝠避障”的輔助軟件,以其創新的設計理念與實用功能…

Offline RL : Context-Former: Stitching via Latent Conditioned Sequence Modeling

paper 基于HIM的離線RL算法,解決基于序列模型的離線強化學習算法缺乏對序列拼接能力。 Intro 文章提出了ContextFormer,旨在解決決策變換器(Decision Transformer, DT)在軌跡拼接(stitching)能力上的不足…

新定義單片機的說明

新定義的官網是https://www.rdsmcu.com/shop/#/,主要經營的是1T系列的51單片機,之前從他們官網上申請了評估板,自己頁玩了一段時間,不過玩的不多,特開此專欄記錄學習過程,并幫助剛入門的道友快速上手。 我申請的是評估…

DQL(數據查詢)

目錄 1. DQL概念 2. DQL - 編寫順序 3. 基礎查詢 3.1 查詢多個字段 3.2 字段設置別名 3.3 去除重復記錄 3.4 案例 4. 條件查詢 4.1 語法 4.2 條件 4.3 案例: 5. 聚合函數 5.1 常見的聚合函數: 5.2 語法 5.3 案例: 6. 分組查…

VScode SSH連接遠程服務器報錯

一、報錯 通過VScode SSH插件遠程連接服務器,輸入密碼后沒有連接成功,一直跳出輸入密碼界面,在輸出界面里,一直是Waiting for server log或者是顯示Cannot not find minimist 二、處理 🐱: 這個時候應該…

力扣每日一題 5/25

題目: 給你一個下標從 0 開始、長度為 n 的整數數組 nums ,以及整數 indexDifference 和整數 valueDifference 。 你的任務是從范圍 [0, n - 1] 內找出 2 個滿足下述所有條件的下標 i 和 j : abs(i - j) > indexDifference 且abs(nums…

CTF網絡安全大賽web題目:字符?正則?

題目來源于&#xff1a;bugku 題目難度&#xff1a;難 題目描  述: 字符&#xff1f;正則&#xff1f; 題目htmnl源代碼&#xff1a; <code><span style"color: #000000"> <span style"color: #0000BB"><?php <br />highl…

C-數據結構-鏈式存儲棧(二次封裝)

/* 二次封裝 借用已經實現雙向鏈表結構來實現 棧 出棧入棧操作類似于 從頭節點開始的插入和刪除 */ llist.h #ifndef LLIST_H__ #define LLSIT_H__ #define LLIST_FORWARD 1 #definr LLIST_BACKWARD 2 typedef void llist_op(const void *);//回調函數 typedef int llist_cmp…

分組排序取最大sql理解

分組排序取最大sql理解 --用戶過濾&#xff08;只能看到當前用戶對應部門用戶權限表中的部門&#xff09; select h.pk_tbdept from jygyl_bmyhqxb h left join jygyl_bmyhqxb_b b on h.pk_bmyhqx b.pk_bmyhqx where isnull(h.dr,0) 0 and isnull(b.dr,0) 0 and b.pk…

類圖的六大關系

類圖中的六大關系包括&#xff1a;繼承關系、實現關系、關聯關系、聚合關系、組合關系和依賴關系。 1. 繼承關系 繼承是一種類與類之間的關系&#xff0c;表示一種泛化和特化的關系。子類繼承父類的特性和行為。 class Animal {void eat() {System.out.println("This an…

TensorFlow.js

什么是 TensorFlow.js&#xff1f; TensorFlow.js 是一個基于 JavaScript 的機器學習庫&#xff0c;它是 Google 開發的 TensorFlow 的 JavaScript 版本。它使得開發者能夠在瀏覽器中直接運行機器學習模型&#xff0c;而不需要依賴于后端服務器或云服務。TensorFlow.js 的主要…

【JavaEE 初階(十)】JVM

?博主主頁: 33的博客? ??文章專欄分類:JavaEE?? &#x1f69a;我的代碼倉庫: 33的代碼倉庫&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;關注我帶你了解更多進階知識 目錄 1.前言2.JVM內存區域劃分3.類加載3.1雙親委派模型 4.垃圾回收&#xff08;GC&#xff0…

【智能優化算法】粒子群優化算法(PSO)【附python實現代碼】

寫在前面&#xff1a; 首先感謝兄弟們的訂閱&#xff0c;讓我有創作的動力&#xff0c;在創作過程我會盡最大能力&#xff0c;保證作品的質量&#xff0c;如果有問題&#xff0c;可以私信我&#xff0c;讓我們攜手共進&#xff0c;共創輝煌。 路雖遠&#xff0c;行則將至&#…