item2:
宏坑多,盡量用const和enum谒撼。類中定義的 static const int val = 0; 只是聲明食寡,如果要取它的地址必須在實(shí)現(xiàn)文件中定義,定義不能有賦值廓潜,因?yàn)槁暶骼锩嬉呀?jīng)賦值了抵皱。
item3:
const std::vector<int>::iterator iter // acts like a T * const
std::vector<int>::const_iterator citer // acts like a const T *
重載運(yùn)算操作符的返回值最好設(shè)成const。函數(shù)的const和非const是不一樣的辩蛋,可以重載呻畸。重載[]操作符最好重載兩個版本〉吭海可以用下面這個方式來減少兩個重載函數(shù)的重復(fù)代碼
const char& operator[](std::size_t position)const
{
...
return text[position];
}
char& operator[](std::size_t position)
{
return const_cast<char&>(
static_cast<const TextBlock&>(*this)[position];
)
}
const成員函數(shù)不能保護(hù)成員指針指向的東西伤为,如果要保護(hù)成員指針指向的東西,要用template寫一個wrapper類据途,重載->操作符(具體看看二樓答案)绞愚。
用mutable聲明的成員變量即使在const對象里面也能被修改。
item4:
用初始化列表來初始化成員函數(shù)效率高颖医。
用函數(shù)中的static對象來解決一個全局對象的初始化依賴另外一個文件的全局對象位衩,而我們不能控制全局對象初始化順序的問題,為了效率可以把這個函數(shù)inline熔萧。
item6:禁止拷貝的方法:將拷貝構(gòu)造函數(shù)和賦值操作符設(shè)為private防止類外調(diào)用糖驴,不實(shí)現(xiàn)它防止friend調(diào)用,將它們聲明在基類可以把錯誤信息從鏈接期移到編譯期哪痰,可以直接繼承boost的noncopyable遂赠。不過如果因此而多重繼承,noncopyable這個空基類的空間優(yōu)化可能會失去晌杰。
item7:凡是用了虛函數(shù)的類一定要有虛析構(gòu)函數(shù)跷睦。反正用了虛函數(shù)都要插多一個vptr,多個虛析構(gòu)也不會增加成本肋演。
std::string, vector, list, set, tr1::unordered_map沒有虛構(gòu)造抑诸,所以不要繼承他。
item8:不要在析構(gòu)函數(shù)里面拋異常爹殊。如果遇到必須拋的情況蜕乡,要處理在析構(gòu)過程中拋異常的情況,應(yīng)該留接口給類用戶在析構(gòu)拋異常的時(shí)候處理好析構(gòu)梗夸。這種做法很丑层玲,沒有優(yōu)雅解決辦法的根源是,析構(gòu)函數(shù)的語義就假設(shè)析構(gòu)過程一定是沒問題的。
item9:不要在構(gòu)造函數(shù)和析構(gòu)函數(shù)里面調(diào)用虛函數(shù)辛块,因?yàn)槟莻€時(shí)候vptr指向基類的虛函數(shù)表畔派。如果構(gòu)造函數(shù)和析構(gòu)函數(shù)是通過另外一個函數(shù)來調(diào)用虛函數(shù),這種錯誤的做法不會被編譯器檢查出來润绵。
item10:賦值操作符要返回*this引用线椰,因?yàn)檫@是賦值運(yùn)算符對于內(nèi)置類型的語義。
item11:
Object& Object::operator=(const Object& rhs)
{
Object *temp = p_obj;
p_obj = new Object(*rhs.p_obj);
delete temp;
return *this;
}
Object& Object::operator=(const Object& rhs)
{
Object temp(rhs);
swap(temp);
return *this;
}
Object& Object::operator=(const Object rhs)
{
swap(rhs);
return *this;
}
注意swap是自己定義的成員函數(shù)尘盼。上面這三種做法能夠防止自己給自己賦值憨愉,因?yàn)閐elete放在后面,也可以防止new拋異常以后卿捎,p_obj指向空資源配紫。
item12:記得在拷貝構(gòu)造函數(shù)和賦值操作符里面調(diào)用 成員對象和基類 的拷貝構(gòu)造函數(shù)和賦值操作符,不然編譯器會給成員對象和基類調(diào)用默認(rèn)拷貝構(gòu)造函數(shù)而且不調(diào)用他們的賦值操作符午阵”恳希拷貝構(gòu)造函數(shù)和賦值操作符有相同代碼的時(shí)候,不應(yīng)該讓他們互相調(diào)用趟庄,而是將重復(fù)代碼放在第三個函數(shù)里面括细。
item13:
用構(gòu)造函數(shù)和析構(gòu)函數(shù)的語義來管理資源,以防止多處return或者函數(shù)中間拋異常而引起資源泄漏的情況(RAII戚啥,Resources Acquisition Is Initialization)奋单。
auto_ptr不能用于STL容器,因?yàn)槿绻截愌b著auto_ptr的容器猫十,原來的容器里面的auto_ptr全部被設(shè)成null览濒。但shared_ptr就沒問題。但兩個智能指針都不能用于數(shù)組拖云。
item14:對于管理資源的類贷笛,要好好考慮他被拷貝的情況≈嫦睿可以禁止拷貝乏苦,可以用引用技術(shù)∮瓤穑可以把資源也拷貝過去汇荐。可以改變所有權(quán)(像auto_ptr)盆繁。
item15:
有時(shí)候一些函數(shù)只能以裸指針作為參數(shù)掀淘。這個時(shí)候可以像auto_ptr和shared_ptr那樣提供一個get方法,也可以定義隱式類型轉(zhuǎn)換油昂。
順帶一提革娄,造智能指針的時(shí)候有應(yīng)該重載指針的應(yīng)有的api倾贰,這些api有 * -> ->*
item17:
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
當(dāng)該statement以這樣的順序執(zhí)行:1 new Widget. 2 運(yùn)行priority() 3 把new出來的對象轉(zhuǎn)換成shared_ptr然后調(diào)用shared_ptr的拷貝構(gòu)造函數(shù),而priority又拋異常拦惋,那new出來的資源就泄漏了躁染。所以應(yīng)該把new出來的資源交給智能指針的語句單獨(dú)放在一行里面。
item18: 如果接口容易用錯(例如傳錯參數(shù))架忌,嘗試用C++內(nèi)置的類型檢查讓編譯器來為我們檢查這些錯誤(引入新類型,用const)我衬。盡量讓接口保持一致(命名一致叹放,同名函數(shù)功能相似,重載操作符行為與內(nèi)置的一致)挠羔。
item22:書中說把成員變量設(shè)成private井仰。其實(shí)我覺得這不重要,關(guān)鍵還是接口應(yīng)該設(shè)計(jì)得夠簡單而且不那么需要改來改去破加。
item23:不要把跟類有關(guān)的函數(shù)都塞進(jìn)類里面俱恶。類應(yīng)該緊湊,這樣寫類的時(shí)候腦子不會爆掉范舀。
item24:像封裝數(shù)學(xué)運(yùn)算合是,由于要支持兩種類型的二元運(yùn)算,所以運(yùn)算函數(shù)不能是成員函數(shù)锭环,因?yàn)檫@樣會限制實(shí)現(xiàn)二元運(yùn)算的交換率聪全。
item25:如果要定制std::swap,最好像STL容器那樣先定義一個public的swap為成員函數(shù)辅辩,然后std命名空間下定義一個swap來調(diào)用這個成員函數(shù)swap难礼。如果這個swap也需要模板參數(shù),由于模板函數(shù)不支持partial specialization, 所以我們應(yīng)該重載swap函數(shù)玫锋,而且要把非成員版本放在該類的命名空間下(因?yàn)椴荒茉趕td里面加template)
template<typename T>
void swap(Widget<T> &a, Widget<T>&b) // 這里原來是 T &a, T & b
{
a.swap(b);
}
item26:變量應(yīng)該在用的時(shí)候才定義蛾茉。
item27:盡可能不用cast。即使用也盡可能用C++新的cast撩鹿。注意不能通過下面的方式調(diào)用基類函數(shù)谦炬,因?yàn)閏ast會產(chǎn)生新的臨時(shí)對象,函數(shù)調(diào)用不會在當(dāng)前對象中起作用
static_cast<Base>(*this).memfun();
item28:對象方法返回一個指向?qū)ο髢?nèi)部成員的指針节沦,引用吧寺,迭代器時(shí),要小心外部可以通過這個對象方法來修改這些成員(可以通過返回const來解決)散劫,更要小心臨時(shí)對象調(diào)用這個函數(shù)而讓一個外部指針稚机,引用,迭代其指向一個被析構(gòu)對象的內(nèi)部成員获搏。
item29:非常精華和重要的有一節(jié)赖条!
如果一個函數(shù)調(diào)用到一半拋異常后失乾,Exception-safe有三個層次的保證:
1:程序仍然處于一個valid的狀態(tài)。但是具體處于什么常態(tài)不清楚纬乍。
2:程序處于未調(diào)用該函數(shù)前的狀態(tài)碱茁。
3:程序壓根不拋異常。
如果資源在函數(shù)開頭分配仿贬,在函數(shù)結(jié)束時(shí)釋放纽竣,應(yīng)該用RAII。如果要將某個指針指向一個新資源茧泪,析構(gòu)原來的資源蜓氨,可以像shared_ptr那樣用reset。還可以用copy and swap队伟,或者保證先分配好資源并且配置妥當(dāng)穴吹,再釋放原來的資源(這樣即使分配資源或者配置狀態(tài)失敗的時(shí)候,不會改變原來的狀態(tài))嗜侮。
struct PMImpl
{
std::shared_ptr<Image> bgImage;
int imageChanges;
};
class PrettyMenu
{
...
private:
Mutex mutex;
std::shared_ptr<PMImpl> pImpl;
};
void PrettyMenu::changeBackground(std::istream & imagSrc)
{
using std::swap;
Lock m1(&mutex);
std::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl));
pNew->reset(new Image(imagSrc));
++pNew->imageChanges;
swap(pImpl, pNew);
}
基本的想法是港令,如果資源分配好,變量也配置好锈颗,就交換整個狀態(tài)顷霹。
如果一個函數(shù)不是Exception-safe,那么它在拋異常以后击吱,狀態(tài)的不確定就導(dǎo)致整個程序的狀態(tài)不確定泼返。
item30:調(diào)用虛函數(shù)的函數(shù)不能是inline。
item31: "pimpl idiom"(把一個類分成兩個姨拥,一個負(fù)責(zé)實(shí)現(xiàn)绅喉,一個通過提供指向?qū)崿F(xiàn)類對象的private指針和調(diào)用實(shí)現(xiàn)類方法的public函數(shù)提供接口。為的是防止一個頭文件修改導(dǎo)致n個文件要重新編譯)的做法真的好丑叫乌。不過柴罐,當(dāng)一個類僅僅用于組合其它類的話,盡量用指針確實(shí)是個不錯的選擇憨奸。
在聲明函數(shù)時(shí)革屠,即使對象以值傳遞的方式傳給函數(shù)或者從函數(shù)返回,這個文件也不需要該類定義排宰,只需要類聲明似芝。因?yàn)槭呛瘮?shù)聲明而不是實(shí)現(xiàn),不需要知道類的大小板甘。
三種方式減少編譯文件之前的依賴:
1 如果可以党瓮,在頭文件中用 指針或者引用。
2 在頭文件中進(jìn)可能用類聲明盐类,而不是類定義寞奸。
3 將聲明和定義類的放在不同的頭文件里面呛谜。
可以使用工廠函數(shù)返回對象指針可以減少編譯文件間的依賴。
item32:繼承的語義是枪萄,子類擁有任何基類的行為隐岛。有時(shí)候會發(fā)現(xiàn)子類不應(yīng)該擁有基類某個方法或者在完成一項(xiàng)任務(wù)的時(shí)候應(yīng)該擁有與基類不一樣的函數(shù)。這不是設(shè)計(jì)得好不好的問題瓷翻,而是繼承的語義本來就沒那么通用聚凹。
item33:可以在public: 里面using Base::memfun; 來讓被自己重載函數(shù)隱藏的基類同名函數(shù)重見天日。
item34:純虛函數(shù)的語義是定義通用接口(子類必須自己定義行為)齐帚。虛函數(shù)的語義是定義通用接口還有默認(rèn)行為妒牙。非虛函數(shù)的語義是子類不應(yīng)該改變的行為。
純虛函數(shù)可以定義童谒,但是子類必須顯示調(diào)用(Base:memfun())。這種用法可以用來提供一個通用接口和默認(rèn)行為沪羔,但是使用默認(rèn)行為的時(shí)候必須顯示掉用饥伊,這樣又可以提醒需要自定義行為的子類重新定義該方法。
item35:
1 基類不直接定義public的虛函數(shù)蔫饰,而是把它設(shè)為private琅豆,而讓一個public的非虛函數(shù)調(diào)用這個虛函數(shù)。好處是通過在public的非虛函數(shù)里面可以放一些設(shè)置配置調(diào)用環(huán)境的代碼來保證子類在調(diào)用這個虛函數(shù)時(shí)處于正確的調(diào)用環(huán)境中篓吁。
2 將部分邏輯寫在類外的一個函數(shù)里面茫因,在構(gòu)造函數(shù)中通過傳入這個函數(shù)的方式,能讓沒一個類的對象有不同的行為杖剪。而且可以在運(yùn)行期改變對象的行為冻押。或者用boost的function和bind盛嘿。
3 跟2差不多洛巢,只不過將類外的函數(shù)寫進(jìn)一個用類包裹的繼承體系里面(喔,類也能這么靈活次兆。不過相比與lambda和boost的function稿茉,這種做法就顯得有點(diǎn)臃腫了)。
item36:不要重定義基類的非虛函數(shù)芥炭。
item37:不要在改變虛函數(shù)里面用默認(rèn)參數(shù)漓库。因?yàn)檫@里有個陷阱,一個基類指針在調(diào)用子類虛函數(shù)的時(shí)候會用基類的默認(rèn)參數(shù)园蝠!
item39:private繼承表達(dá)的是以什么類來實(shí)現(xiàn)(只重用實(shí)現(xiàn)渺蒿,不重用接口)。通過將一個虛函數(shù)放進(jìn)內(nèi)部類里面彪薛,子類就不能修改這個虛函數(shù)(子類是可以override基類的private虛函數(shù)的)蘸嘶。private繼承空基類的時(shí)候良瞧,會有empty base optimization(EBO), 這樣編譯器不會為空類插入一個char還有padding。private繼承還可以讓子類override基類虛函數(shù)還有調(diào)用基類protected的成員训唱。
item40:從對象模型來看褥蚯,要實(shí)現(xiàn)多重繼承和虛擬繼承的語義,坑實(shí)在是太多了况增。指針要轉(zhuǎn)來轉(zhuǎn)去赞庶,運(yùn)行的成本很大。如果不在意效率澳骤,通過public繼承接口和private繼承實(shí)現(xiàn)確實(shí)很方便歧强。
item41:類與模板都支持接口和多態(tài)(雖然我腦子里的模板多態(tài)指static polymorphism)。模板的implicit接口真的非常強(qiáng)大为肮,特別是加上C++11的auto(好吧摊册,這對動態(tài)語言來說再平常不過)。
item42:nested dependent name在默認(rèn)情況下不會被編譯器認(rèn)為是類型颊艳,而是一個類中的成員茅特,因此需要在類型前面加typename說明。但是他不能用于存在與繼承列表和初始化列表的基類的nested dependent name棋枕。
item43:在繼承一個模板基類的時(shí)候(同時(shí)子類也是模板類)白修,由于這個模板基類可能存在specialization,對于不同的模板參數(shù)重斑,基類的實(shí)現(xiàn)可能完全不一樣兵睛!一個函數(shù)可以在general的實(shí)現(xiàn)有,在specialization的版本沒有窥浪。如果子類調(diào)用了這個函數(shù)(直接使用memfun();)祖很,編譯器會報(bào)錯說沒有這個函數(shù)(因?yàn)榭赡苷娴臎]有)。解決方法有三個:
1 把memefun(); 改成 this->memfun(); 這樣編譯器會假設(shè)有漾脂,如果沒有突琳,編譯器是在模板被instantiated 的時(shí)候報(bào)錯。
2 像item33那樣 using Base<T>::memfun(); 他讓編譯器在instantiation之前就去搜索這個函數(shù)符相。
3 把memfun(); 改成Base<T>::memfun(); 注意這種做法會讓虛函數(shù)失效拆融。
item44:模板中與模板參數(shù)無關(guān)的代碼隨著各種版本instantiate,會讓編譯出來的程序膨脹啊终。解決辦法是可以把這些參數(shù)放進(jìn)函數(shù)里面镜豹。是否會讓程序變快變慢要看實(shí)際。
item45: 非常tricky
template<typename T>
class SmartPtr
{
public:
template<typename U>
SmartPtr(const SmartPtr<U>& other):heldPtr(other.get())
{...}
T* get()const{ return heldPtr; }
private:
T *heldPtr;
};
這樣蓝牲,只有U版本的heldPtr能夠隱式轉(zhuǎn)換成T版本的heldPtr趟脂,編譯才會通過,這樣就可以通過內(nèi)置的隱式類型轉(zhuǎn)換例衍,讓自己構(gòu)造的類也能隱式類型轉(zhuǎn)換昔期。這種做法可以用于賦值操作符已卸,拷貝構(gòu)造函數(shù),也可以用于類型轉(zhuǎn)換操作符硼一。不過當(dāng)T 和 U一樣的時(shí)候累澡,編譯器不會造一個普通賦值操作符,拷貝構(gòu)造函數(shù)般贼!需要自己重新寫一個愧哟。
item46:
當(dāng)不用模板的時(shí)候(模板函數(shù)加上模板類),在調(diào)用參數(shù)的時(shí)候參數(shù)可以通過構(gòu)造函數(shù)隱式類型轉(zhuǎn)換來得到函數(shù)所需要的參數(shù)類型哼蛆。但是在模板參數(shù)類型推導(dǎo)的時(shí)候蕊梧,編譯器不能讓通過構(gòu)造函數(shù)的隱式類型轉(zhuǎn)換發(fā)生。