條款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)銷(xiāo)叉橱,雖然在這個(gè)例子中微乎其微挫以。
在定義一個(gè)類(lèi)對(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++中新型的類(lèi)型轉(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è)基類(lèi)型指針轉(zhuǎn)換為子類(lèi)型指針,存在額外開(kāi)銷(xiāo)袭灯,并且需要判斷轉(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è)類(lèi)中如果定義了private成員變量蟋恬,但同時(shí)又通過(guò)public成員函數(shù)返回了其指針或者引用翁潘,這樣就會(huì)間接的破壞了類(lèi)的封裝性,原本private成員變量會(huì)被外部客戶(hù)直接使用歼争。
- 返回類(lèi)中成員變量的引用或者指針時(shí)拜马,會(huì)增加“懸垂指針”問(wèn)題的產(chǎn)生的風(fēng)險(xiǎn)。因?yàn)殂迦蓿?dāng)外部代碼獲取到這些引用或者指針后俩莽,其對(duì)應(yīng)的類(lèi)實(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)銷(xiāo);
- 帶來(lái)的問(wèn)題是會(huì)增加程序代碼的大小匕荸,因?yàn)闀?huì)在調(diào)用的地方展開(kāi)爹谭。所以一般是將常用的并且短小的函數(shù)設(shè)置成inline。
- class類(lèi)定義體中的實(shí)現(xiàn)函數(shù)默認(rèn)設(shè)置成inline方式榛搔。
- 函數(shù)模板不要設(shè)置成inline方式诺凡,雖然一般是在頭文件中定義。
條款31:將文件間的編譯依存關(guān)系降至最低
我們?cè)诼暶饕粋€(gè)類(lèi)時(shí)践惑,如果成員變量中包含了其它類(lèi)時(shí)腹泌,則需要引用(include)其它類(lèi)的頭文件,因?yàn)榫幾g器在編譯這個(gè)類(lèi)時(shí)需要知道每個(gè)成員變量占用多大的空間童本。在這里真屯,該類(lèi)的聲明就在編譯層面與其它類(lèi)產(chǎn)生了依存關(guān)系脸候。
為了減少這種依存關(guān)系穷娱,在類(lèi)的定義中需要避免在成員變量中定義其它類(lèi)的對(duì)象,如果一定要定義运沦,最好采用引用或者指針(包括智能指針)形式泵额。這時(shí)候,可以在類(lèi)的頭文件中采用前置聲明的方式來(lái)引用其它類(lèi)携添,而不是直接inlcude其它頭文件嫁盲。
舉例:存在一個(gè)Date類(lèi)表示日期,Address類(lèi)表示地址烈掠,以及Person類(lèi)表示一個(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)文件中需要引用涉及到的類(lèi)頭文件瘾蛋。
通過(guò)這種方式,將類(lèi)的編譯從相依于定義式轉(zhuǎn)變?yōu)橄嘁烙诼暶魇健?/p>
在接口類(lèi)的設(shè)計(jì)中矫限,為了盡可能降低接口類(lèi)編譯的依存關(guān)系哺哼,也采用了這種思路,有兩種做法:
- handle class:在接口類(lèi)中定義一個(gè)實(shí)現(xiàn)類(lèi)的handle(引用或者指針)叼风,由實(shí)現(xiàn)類(lèi)完成與其他類(lèi)的編譯依存關(guān)系取董。
- interface class:接口類(lèi)只包含虛函數(shù)功能接口,由具體的實(shí)現(xiàn)類(lèi)繼承接口類(lèi)進(jìn)行實(shí)現(xiàn)无宿,采用簡(jiǎn)單工廠模式創(chuàng)建具體的實(shí)現(xiàn)類(lèi)對(duì)象茵汰。