Effective C++學(xué)習(xí)筆記(第五章)

條款26:盡可能延后變量定義式的出現(xiàn)時(shí)間
  • 考察下面的示例代碼:
void Foo()
{
  std::string myStr;
  if (條件) {
    // 沒(méi)用用到myStr
    return;
  }
  // 使用myStr變量的代碼
}

很顯然,這里的myStr提前定義了,并且會(huì)帶來(lái)額外的默認(rèn)構(gòu)造函數(shù)的開(kāi)銷,雖然在這個(gè)例子中微乎其微。

  • 在定義一個(gè)類對(duì)象時(shí),盡可能使用其帶參數(shù)的構(gòu)造函數(shù),而不是先使用默認(rèn)構(gòu)造函數(shù)然后再使用賦值語(yǔ)句操作。

  • 在for循環(huán)中用到的臨時(shí)變量最好是在循環(huán)體中定義,這樣做的好處是臨時(shí)變量的作用域只在循環(huán)體中,避免了與外部變量的沖突。

for (int i = 0; i < N; i++) {
  MyClass obj;  // 在循環(huán)體內(nèi)定義變量,這是建議的做法。
  // 循環(huán)體代碼
}
條款27:盡量少做轉(zhuǎn)型動(dòng)作

C++中新型的類型轉(zhuǎn)換有:

  • const_cast:用于去掉const變量的const屬性,但是如果通過(guò)轉(zhuǎn)換后的非const指針去修改一個(gè)const變量是造成未定義的后果。
const int val = 100;
int *p = const_cast<int*>(&val);
*p = 101; // undefined behavior

詳見(jiàn):https://en.cppreference.com/w/cpp/language/const_cast
PS:個(gè)人覺(jué)得const_cast有點(diǎn)讓人摸不著頭腦,不知道為啥要設(shè)計(jì)這個(gè)轉(zhuǎn)換。

  • dynamic_cast:“安全向下”轉(zhuǎn)型,用于將一個(gè)基類型指針轉(zhuǎn)換為子類型指針,存在額外開(kāi)銷,并且需要判斷轉(zhuǎn)換后的結(jié)果是否為空。
class B {
public:
    B() {}
    virtual ~B() {}
};

class D : public B {

};

int main()
{
    B* pb1 = new D();
    B* pb2 = new B();

    D* pd1 = dynamic_cast<D*>(pb1);
    if (pd1 != nullptr) {
        std::cout << "pd1 is ref to D obj" << std::endl;
    } else {
        std::cout << "pd1 is not ref to D obj" << std::endl;
    }

    D* pd2 = dynamic_cast<D*>(pb2);
    if (pd2 != nullptr) {
        std::cout << "pd2 is ref to D obj" << std::endl;
    } else {
        std::cout << "pd2 is not ref to D obj" << std::endl;
    }

    return 0;
}
  • reinterpret_cast:常用于重新解釋一個(gè)數(shù)據(jù)結(jié)構(gòu),存在比較大的安全風(fēng)險(xiǎn)。
  • statitc_cast:對(duì)應(yīng)于C語(yǔ)言的中強(qiáng)制轉(zhuǎn)換。

使用建議:

  • 盡可能使用C++新的轉(zhuǎn)型關(guān)鍵字
  • 如無(wú)必要,盡量減少轉(zhuǎn)型,特別是dynamic_cast這種轉(zhuǎn)換,影響性能。
條款28:避免返回handles指向?qū)ο髢?nèi)部成分
  • 一個(gè)類中如果定義了private成員變量,但同時(shí)又通過(guò)public成員函數(shù)返回了其指針或者引用,這樣就會(huì)間接的破壞了類的封裝性,原本private成員變量會(huì)被外部客戶直接使用。
  • 返回類中成員變量的引用或者指針時(shí),會(huì)增加“懸垂指針”問(wèn)題的產(chǎn)生的風(fēng)險(xiǎn)。因?yàn)椋?dāng)外部代碼獲取到這些引用或者指針后,其對(duì)應(yīng)的類實(shí)例對(duì)象可能在其它地方已經(jīng)釋放了。
條款29:為“異常安全”而努力是值得的

異常安全的函數(shù)需要實(shí)現(xiàn)以下要求:

  • 不泄露任何資源:當(dāng)異常發(fā)生后,之前分配的資源不應(yīng)該沒(méi)有釋放。
  • 不允許數(shù)據(jù)敗壞,主要是異常發(fā)生后要保證數(shù)據(jù)的一致性。

異常安全有三個(gè)級(jí)別的承諾:

  • 基本承諾:異常發(fā)生后,系統(tǒng)的狀態(tài)不會(huì)出現(xiàn)不一致的情況,但是具體是什么狀態(tài)無(wú)法保證。
  • 強(qiáng)烈保證:要么完全成功,要么退回到函數(shù)調(diào)用前的狀態(tài)。
  • 不拋異常保證:函數(shù)執(zhí)行過(guò)程中保證不產(chǎn)生異常。

常用的異常安全實(shí)現(xiàn)方法是“copy and swap”方法,該方法步驟如下:
1、為你將要修改的對(duì)象,先建立一個(gè)副本;
2、在副本上進(jìn)行需要的修改;
3、將原對(duì)象與副本對(duì)象進(jìn)行置換操作,并且保證調(diào)用的是一個(gè)不會(huì)拋出異常的swap操作,通常是兩個(gè)指針變量之間的交換;

條款30:透徹了解inlining的里里外外
  • inline函數(shù)的好處是可以像函數(shù)那樣調(diào)用,但是卻沒(méi)有函數(shù)調(diào)用的開(kāi)銷;
  • 帶來(lái)的問(wèn)題是會(huì)增加程序代碼的大小,因?yàn)闀?huì)在調(diào)用的地方展開(kāi)。所以一般是將常用的并且短小的函數(shù)設(shè)置成inline。
  • class類定義體中的實(shí)現(xiàn)函數(shù)默認(rèn)設(shè)置成inline方式。
  • 函數(shù)模板不要設(shè)置成inline方式,雖然一般是在頭文件中定義。
條款31:將文件間的編譯依存關(guān)系降至最低

我們?cè)诼暶饕粋€(gè)類時(shí),如果成員變量中包含了其它類時(shí),則需要引用(include)其它類的頭文件,因?yàn)榫幾g器在編譯這個(gè)類時(shí)需要知道每個(gè)成員變量占用多大的空間。在這里,該類的聲明就在編譯層面與其它類產(chǎn)生了依存關(guān)系。

為了減少這種依存關(guān)系,在類的定義中需要避免在成員變量中定義其它類的對(duì)象,如果一定要定義,最好采用引用或者指針(包括智能指針)形式。這時(shí)候,可以在類的頭文件中采用前置聲明的方式來(lái)引用其它類,而不是直接inlcude其它頭文件。

舉例:存在一個(gè)Date類表示日期,Address類表示地址,以及Person類表示一個(gè)人,并且提供獲取此人生日和地址的方法。

  • Date.h
#ifndef UNTITLED6_DATE_H
#define UNTITLED6_DATE_H
class Date {
};

#endif //UNTITLED6_DATE_H
  • Address.h
#ifndef UNTITLED6_ADDRESS_H
#define UNTITLED6_ADDRESS_H
class Address {
};
#endif //UNTITLED6_ADDRESS_H
  • Person.h
#ifndef UNTITLED6_PERSON_H
#define UNTITLED6_PERSON_H
class Address; // 前置聲明
class Date;  // 前置聲明
class Person {
public:
    Address GetAddr();
    Date GetDate();
};
#endif //UNTITLED6_PERSON_H
  • Person.cpp
#include "Person.h"
#include "Address.h"
#include "Date.h"

Address Person::GetAddr()
{
    Address tmp;

    return tmp;
}

Date Person::GetDate()
{
    Date tmp;

    return tmp;
}

可以看到Person.h中并沒(méi)有include Date.h以及Address.h,但是在cpp實(shí)現(xiàn)文件中需要引用涉及到的類頭文件。

通過(guò)這種方式,將類的編譯從相依于定義式轉(zhuǎn)變?yōu)橄嘁烙诼暶魇健?/p>

在接口類的設(shè)計(jì)中,為了盡可能降低接口類編譯的依存關(guān)系,也采用了這種思路,有兩種做法:

  • handle class:在接口類中定義一個(gè)實(shí)現(xiàn)類的handle(引用或者指針),由實(shí)現(xiàn)類完成與其他類的編譯依存關(guān)系。
  • interface class:接口類只包含虛函數(shù)功能接口,由具體的實(shí)現(xiàn)類繼承接口類進(jìn)行實(shí)現(xiàn),采用簡(jiǎn)單工廠模式創(chuàng)建具體的實(shí)現(xiàn)類對(duì)象。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容