條款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ì)象。