effective c++_【阅读笔记】Effective C++()
全文參考自Effective C++, Scott Meyers
程序來(lái)自本人
https://github.com/hxz1998/ccl
1. 讓自己習(xí)慣C++
C++高效編程守則視狀況而變化,取決于使用C++的哪一部分。
C++四大塊:
- C
- Object-Oriented C++:面向?qū)ο?/li>
- Template C++:泛型編程,模板元編程
- STL:容器,迭代器,算法,函數(shù)對(duì)象
2. 盡量以const,enum,inline替換 #define
- 對(duì)于單純常量,最好使用 const 或者 enums 來(lái)替換 #define
- 對(duì)于形似函數(shù)的宏(macros),最好使用 inline 函數(shù)來(lái)替換 #define
有一種方法,可以獲得宏帶來(lái)的效率,以及一般函數(shù)帶來(lái)的可預(yù)料行為以及類(lèi)型安全性(Type Safety),例如:
template<typename?T>inline?T?callWithMax(const?T?&a,?const?T?&b)?{????return?a?>?b???a?:?b;
}
int?main()?{
????int?a?=?callWithMax<int>(1,?2);
????cout?<endl;
}
3. 盡可能使用 const
- 聲明為 const 可以讓編譯器幫助檢查錯(cuò)誤。
- const 可以施加于任何作用域內(nèi)的對(duì)象、函數(shù)參數(shù)、函數(shù)返回類(lèi)型、成員函數(shù)。
- 編譯器強(qiáng)制實(shí)施bitwise constness,但編寫(xiě)程序時(shí),應(yīng)該使用“概念上的常量性”conceptual constness。
- 當(dāng) const 和 non-const 成員函數(shù)有實(shí)質(zhì)等價(jià)的實(shí)現(xiàn)時(shí),要用 non-const 版本去調(diào)用 const 版本,這樣可減少代碼重復(fù)。
const 如果出現(xiàn)在 * 左邊,那么表示被指物是常量;如果在 * 右邊,那么表示指針是常量;如果出現(xiàn)在兩邊,那么表示指針和被指物都是常量。例如:
int?main()?{????int?a?=?0,?b?=?1;
????int?const?*p1?=?&a;
????int?*const?p2?=?&a;
????*p1?=?2;????//?不行!因?yàn)?p1 指向的內(nèi)容是常量
????*p2?=?2;????//?可以,p2?自身是常量,p2?只能指向a,但是?a?中的內(nèi)容可以變
????p1?=?&b;????//?可以,p1?指向另一個(gè)內(nèi)容,并聲稱(chēng)這個(gè)內(nèi)容不可變
????p2?=?&b;????//?不可以,p2?自身是常量,不能指向其他東西了
}
試著習(xí)慣這樣的寫(xiě)法:
void?f1(const?int?*i);?//?指向一個(gè)不能修改內(nèi)容的?ivoid?f2(int?const?*i);?//?一貓貓一樣
這倆寫(xiě)法效果是一樣的,都是指向一個(gè)內(nèi)容不可變的數(shù)據(jù)(指針本身可以再修改指向的對(duì)象)。
對(duì)于第三點(diǎn),一個(gè)很好的例子如下:
class?Text?{private:
????std::string?text;
public:
????const?char?&operator[](std::size_t?pos)?const?{
????????return?text[pos];
????}
????char?&operator[](std::size_t?pos)?{
????????return?const_cast<char?&>?(?????????//?使用?const_cast?去掉?const?聲明
????????????????static_cast<const?Text?&>???//?使用?static_cast?把?*this?轉(zhuǎn)換成?const?對(duì)象
????????????????(*this)[pos]);??????????????//?使用?(*this)[]?方法返回(這個(gè)時(shí)候是?const?結(jié)果,經(jīng)過(guò)?const_cast?去掉?const?聲明
????}
};
4. 確定對(duì)象被使用前已被初始化
- 為內(nèi)置對(duì)象進(jìn)行手工初始化,因?yàn)镃++并不能保證完全初始化好它們。
- 構(gòu)造函數(shù)最好使用成員初值列(member initialization list),而不要在構(gòu)造函數(shù)本體內(nèi)使用賦值操作(assignment)。
- 初始列列出的成員變量,其排列順序要和它們?cè)陬?lèi)聲明中的一致。
- 為免除“跨編譯單元初始化次序”問(wèn)題,使用 local static 對(duì)象來(lái)代替 non-local static 對(duì)象。
如果成員變量是 const 的或者 references 的,那么它們一定需要有初值,不能被賦值。例如:
class?X?{????const?int?val;
????int?&re_val;
public:
????X(int?val_,?int?&re_val_)?:?val(val_),?re_val(re_val_)?{}
};
基類(lèi)總是比派生類(lèi)要先初始化好,例如:
class?X?{????const?int?val;
????int?&re_val;
public:
????X(int?val_,?int?&re_val_)?:?val(val_),?re_val(re_val_)?{
????????cout?<"X?initialization..."?<endl;
????}
};
class?Y?:?public?X?{
public:
????Y(int?val,?int?&re_val)?:?X(val,?re_val)?{
????????cout?<"Y?initialization..."?<endl;
????};
};
int?main()?{
????int?re_v?=?1;
????Y?y(1,?re_v);
????//?>:?X?initialization...
????//????Y?initialization...
}
由于定義于不同編譯單元內(nèi)的 non-local static 對(duì)象的初始化順序并未明確定義,因此會(huì)出現(xiàn)這樣情況:
- 定義在 File1.hh 中一個(gè)靜態(tài)全局變量 tfs
- 在 File2.cc 中使用 tfs
那么如果 File1.hh 中的 tfs 還沒(méi)初始化好呢,File2.cc 中就想使用了,那么就會(huì)出現(xiàn)大問(wèn)題!例如下面這個(gè)例子:
//?File1.hh#include?
using?namespace?std;
class?FileSystem?{
public:
????size_t?numDisks()?const?{?return?0;?}
};
extern?FileSystem?tfs;
//?File2.cc
#include?
#include?"File1.hh"
using?namespace?std;
class?Directory?{
????size_t?disks;
public:
????Directory()?{?disks?=?tfs.numDisks();?}
????size_t?getDisks()?const?{?return?disks;?}
};
int?main()?{
????Directory?directory;
????cout?<}
這個(gè)時(shí)候編譯器就會(huì)報(bào)錯(cuò):
CMakeFiles\local_static.dir/objects.a(File2.cc.obj):File2.cc:(.rdata$.refptr.tfs[.refptr.tfs]+0x0):?undefined?reference?to?`tfs'很明顯,在 local_static 目錄中沒(méi)有找到該引用,因此報(bào)錯(cuò)了,那么該怎樣做呢?
使用方法(類(lèi)似于工廠方法)來(lái)獲取這個(gè)值,而不是依賴(lài)編譯器初始化
例如:
//?File1.hhclass?FileSystem?{
public:
????size_t?numDisks()?const?{?return?0;?}
};
FileSystem?&getFS()?{
????static?FileSystem?tfs;
????return?tfs;
}
//?File2.cc
class?Directory?{
????size_t?disks;
public:
????Directory()?{?disks?=?getFS().numDisks();?}
????size_t?getDisks()?const?{?return?disks;?}
};
int?main()?{
????Directory?directory;
????cout?<}
這樣一來(lái),就不用擔(dān)心了?。
不過(guò),這樣還是有另外一個(gè)問(wèn)題,例如多線程環(huán)境下還是有不確定情況,處理這種麻煩情況的做法之一是:在單線程啟動(dòng)階段,手動(dòng)調(diào)用一遍所有的 reference-returning 方法。這樣可以消除與初始化有關(guān)的“競(jìng)速形式(race conditions)”
5. 了解C++默默編寫(xiě)并調(diào)用哪些函數(shù)?
編譯器可以暗自為 class 創(chuàng)建 default 構(gòu)造函數(shù)、copy 構(gòu)造函數(shù),copy assignment 操作符,以及析構(gòu)函數(shù)。
首先,開(kāi)門(mén)見(jiàn)山地說(shuō),C++默認(rèn)編寫(xiě)了默認(rèn)構(gòu)造函數(shù)、默認(rèn)析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù),以及拷貝賦值函數(shù),而且它們默認(rèn)都是 inline 的。當(dāng)然,這些函數(shù)的默認(rèn)創(chuàng)建在一定時(shí)期是失效的,例如:
- 默認(rèn)構(gòu)造函數(shù):當(dāng)提供了一個(gè)構(gòu)造函數(shù)后,編譯器不再為類(lèi)提供默認(rèn)構(gòu)造函數(shù),而且默認(rèn)。
- 默認(rèn)析構(gòu)函數(shù):當(dāng)提供了一個(gè)析構(gòu)函數(shù)后,編譯器就不再提供默認(rèn)析構(gòu)函數(shù),默認(rèn)析構(gòu)函數(shù)是 non-virtual 的。
- 拷貝構(gòu)造函數(shù):只要沒(méi)提供,而且滿足可拷貝構(gòu)造的條件,那么就提供,否則不提供。
- 拷貝賦值函數(shù):只要沒(méi)提供,而且滿足可拷貝復(fù)制的條件,那么就提供,否則不提供。
上面說(shuō)了兩個(gè)條件,那么具體是什么條件呢?
5.1 可拷貝構(gòu)造&可拷貝賦值?
先來(lái)看一下滿足這倆條件的例子:
template<typename?T>class?NamedObject?{
private:
????T?objectValue;
????string?name;
public:
????NamedObject(string?n,?T?val)?:?name(n),?objectValue(val)?{}
????NamedObject(const?NamedObject?&rhs)?{
????????objectValue?=?rhs.objectValue;
????????name?=?rhs.name?+?"?copy?";
????}friend?ostream?&operator<const?NamedObject?&rhs)?{
????????os?<"?"?<????????return?os;
????}
????NamedObject?&operator=(const?NamedObject?&rhs)?{
????????objectValue?=?rhs.objectValue;
????????name?=?rhs.name?+?"?=?";return?*this;
????}
};int?main()?{string?newDog?=?"newDog";string?oldDog?=?"oldDog";NamedObject<int>?od(oldDog,?1);NamedObject<int>?nd(newDog,?2);cout?<"?:?"?<endl;??//?>:?2?newDog?:?1?oldDog
????nd?=?od;????cout?<//?>:?1?oldDog?=
}
那么此時(shí),即便自己不提供拷貝構(gòu)造以及拷貝賦值構(gòu)造操作符,編譯器也會(huì)對(duì)成員變量進(jìn)行遞歸的拷貝賦值過(guò)來(lái)。但是在遇到成員變量是 const 或者 reference 類(lèi)型時(shí),編譯器就兩手一攤,無(wú)能為力了(具體可參考Effective C++, 3th, P37)。
例如下面的例子:
template<typename?T>class?NamedObject?{
private:
????const?T?objectValue;
????string&?name;
public:
????//?其他函數(shù)都一樣
????NamedObject(const?NamedObject?&rhs)?{
????????objectValue?=?rhs.objectValue;
????????name?=?rhs.name?+?"?copy?";
????}
????NamedObject?&operator=(const?NamedObject?&rhs)?{
????????objectValue?=?rhs.objectValue;?//?不能對(duì)一個(gè) const 對(duì)象賦值!
????????name?=?rhs.name?+?"?=?";return?*this;
????}
};int?main()?{string?newDog?=?"newDog";string?oldDog?=?"oldDog";NamedObject<int>?od(oldDog,?1);NamedObject<int>?nd(newDog,?2);
????nd?=?od;????//?>:?error:?use?of?deleted?function?'NamedObject&?NamedObject::operator=(const?NamedObject&)'
}
當(dāng)然,這只是編譯器不再提供了而已,用戶自己還是可以設(shè)計(jì)如何去復(fù)制拷貝以及構(gòu)造拷貝的,這完全取決于自己怎么處理成員變量。
除此之外,如果基類(lèi)把拷貝構(gòu)造函數(shù)設(shè)置成了 private 那么在派生類(lèi)中也是沒(méi)辦法操作的。
6. 若不想使用編譯器自動(dòng)生成的函數(shù),那該明確拒絕
為駁回編譯器自動(dòng)(暗自)提供的機(jī)能,可將相應(yīng)的成員函數(shù)聲明為 private 并且不予實(shí)現(xiàn)?;蛘呤褂美^承 Uncopyable 這樣的基類(lèi)。
如果不想讓一個(gè)類(lèi)支持拷貝構(gòu)造或者賦值構(gòu)造,那么我們可以將函數(shù)聲明但不實(shí)現(xiàn),例如這樣子:
class?Uncopyable?{private:
????Uncopyable(const?Uncopyable&);
????Uncopyable&?operator=(const?Uncopyable&);
};
當(dāng)然,對(duì)于每一個(gè)想實(shí)現(xiàn)這個(gè)功能的類(lèi)都能去單獨(dú)這樣聲明,不過(guò),還可以使用繼承方法去實(shí)現(xiàn),例如:
class?Uncopyable?{protected:
????Uncopyable()?=?default;
private:
????Uncopyable(const?Uncopyable?&);
????Uncopyable?&operator=(const?Uncopyable?&);
};
class?SubClass?:?public?Uncopyable?{
????//?默認(rèn)不允許拷貝構(gòu)造和賦值運(yùn)算符
};
int?main()?{
????SubClass?s1,?s2;
????s1?=?s2;????//?error!
}
7. 為多態(tài)基類(lèi)聲明virtual析構(gòu)函數(shù)
- 帶多態(tài)性質(zhì)的基類(lèi)應(yīng)該聲明一個(gè) virtual 析構(gòu)函數(shù)。
- 如果類(lèi)帶有任何 virtual 函數(shù),那么它就應(yīng)該擁有一個(gè) virtual 析構(gòu)函數(shù)。
- 如果類(lèi)的設(shè)計(jì)目的不是用來(lái)做基類(lèi)的,那么就不應(yīng)該聲明 virtual 析構(gòu)函數(shù)。
當(dāng)使用基類(lèi)指針指向派生類(lèi)對(duì)象時(shí),沒(méi)有問(wèn)題,但是要是想把這個(gè)基類(lèi)指針給刪掉,這時(shí)候問(wèn)題就來(lái)了,例如下面這個(gè)例子:
class?BaseClass?{private:
????char?*name;
public:
????BaseClass(int?size)?{
????????name?=?new?char[size];
????????for?(int?i?=?0;?i?'a';
????}
?//?基類(lèi)的析構(gòu)函數(shù)
????~BaseClass()?{?delete?name;?}
};
class?DeriveClass?:?public?BaseClass?{
private:
????char?*count;
public:
????DeriveClass(int?size)?:?BaseClass(size)?{
????????count?=?new?char[size];
????????for?(int?i?=?0;?i?'b';
????}
????//?派生類(lèi)的析構(gòu)函數(shù)
????~DeriveClass()?{?delete?count;?}
};
int?main()?{
????//?多態(tài)用法,基類(lèi)指針指向派生類(lèi)對(duì)象,沒(méi)毛病
????BaseClass?*obj?=?new?DeriveClass(16);
????//?刪除基類(lèi)指針,出現(xiàn)了問(wèn)題!
????delete?obj;
????return?0;
}
上面的程序乍一看看不出個(gè)毛病來(lái),現(xiàn)在對(duì) BaseClass *obj = new DeriveClass(16); 設(shè)置斷點(diǎn),進(jìn)行單步調(diào)試,可以觀察到構(gòu)造函數(shù)過(guò)程是:
new?DeriveClass(16)????|
BaseClass(16)
????|
DeriveClass(16)
????|
????end
這個(gè)順序完全正確,先構(gòu)造基類(lèi)再構(gòu)造派生類(lèi)嘛,執(zhí)行完后,內(nèi)存狀態(tài)是這樣的:
可以得知操作系統(tǒng)給這兩個(gè)對(duì)象中的成員分配內(nèi)存到了 name : 0x1061980 和 count : 0x10619c0 。
那么執(zhí)行 delete obj; 時(shí),順序是這樣的:
delete?obj????|
~BaseClass()
????|
????end
從上面可以看出來(lái),竟然只執(zhí)行了基類(lèi)的析構(gòu)函數(shù),而沒(méi)有執(zhí)行派生類(lèi)的析構(gòu)函數(shù),那么這時(shí)的內(nèi)存表示是怎么樣的?見(jiàn)下圖:
由此可見(jiàn),在不經(jīng)意間,就造成了內(nèi)存泄漏問(wèn)題,那么該如何解決這個(gè)問(wèn)題呢?
很簡(jiǎn)單,只需要把基類(lèi)的析構(gòu)函數(shù)聲明為 virtual 就可以了,這樣強(qiáng)制去執(zhí)行子類(lèi)的析構(gòu)函數(shù)。
不過(guò),這樣還是有兩種結(jié)果,例如下面是一種結(jié)果:
class?BaseClass?{????//?其他都一樣
????virtual?~BaseClass()?{?delete?name;?}
};
class?DeriveClass?:?public?BaseClass?{
????//?其他都一樣
????~DeriveClass()?override?{?delete?count;?}
};
這個(gè)時(shí)候,是先執(zhí)行的派生類(lèi)析構(gòu)函數(shù),再執(zhí)行基類(lèi)析構(gòu)函數(shù)。
另一種結(jié)果是:
class?BaseClass?{????virtual?~BaseClass()?{?delete?name;?}
};
class?DeriveClass?:?public?BaseClass?{
????//?刪掉了自己的析構(gòu)函數(shù)
};
這個(gè)情況下,才是先執(zhí)行基類(lèi)析構(gòu)函數(shù),再執(zhí)行派生類(lèi)析構(gòu)函數(shù)。
8. 別讓異常逃離析構(gòu)函數(shù)
- 析構(gòu)函數(shù)絕對(duì)不要拋出異常,如果一個(gè)被析構(gòu)函數(shù)調(diào)用的函數(shù)可能拋出異常,析構(gòu)函數(shù)應(yīng)該捕捉任何異常,然后吞下他們(不傳播)或結(jié)束程序。
- 如果接口使用者需要對(duì)某個(gè)操作函數(shù)運(yùn)行期間拋出的異常做出反應(yīng),那么 class 應(yīng)該提供一個(gè)普通函數(shù)(而不是在析構(gòu)函數(shù)中)執(zhí)行操作。
即便C++允許析構(gòu)函數(shù)拋出異常,但是最好不要這樣做。當(dāng)然,吞掉異常也是有爭(zhēng)議的,比如“草率地結(jié)束程序”可能會(huì)帶來(lái)更嚴(yán)重的問(wèn)題,或者“不明確的行為帶來(lái)的風(fēng)險(xiǎn)”可能會(huì)帶來(lái)不安全的問(wèn)題等等,具體問(wèn)題具體分析是比較好的。
但是,通??梢蕴峁┮粋€(gè)讓用戶在析構(gòu)函數(shù)前控制異常的機(jī)會(huì),例如使用“雙重保險(xiǎn)”來(lái)盡最大化確保問(wèn)題得到解決。
9. 絕不在構(gòu)造函數(shù)和析構(gòu)函數(shù)過(guò)程中調(diào)用virtual函數(shù)
在構(gòu)造和析構(gòu)期間不要調(diào)用 virtual 函數(shù),因?yàn)檫@類(lèi)調(diào)用從不下降至派生類(lèi)。
不管怎樣,都不應(yīng)該在構(gòu)造函數(shù)和析構(gòu)函數(shù)內(nèi)部去調(diào)用一個(gè) virtual 函數(shù),因?yàn)檫@樣的操作是不可預(yù)估的,帶來(lái)意想不到的結(jié)果。為什么這樣?因?yàn)樵诨?lèi)中,構(gòu)造函數(shù)執(zhí)行階段或者析構(gòu)函數(shù)執(zhí)行階段只能看到基類(lèi)的內(nèi)容,所以在派生類(lèi)中實(shí)現(xiàn)的程序,是不可用的。下面這句話直白且有效的指出了問(wèn)題的所在:
在基類(lèi)(base-class)構(gòu)造期間,virtual 函數(shù)不是 virtual 函數(shù)。
也正是因?yàn)檫@樣一個(gè)“對(duì)象在 derived class 構(gòu)造函數(shù)開(kāi)始執(zhí)行前,不會(huì)成為一個(gè) derived class 對(duì)象”的規(guī)則,所以最好在構(gòu)造期間對(duì) virtual 函數(shù)視而不見(jiàn)。
那么如何科學(xué)有效地去解決這個(gè)問(wèn)題?當(dāng)然是在基類(lèi)中把需要在構(gòu)造函數(shù)內(nèi)執(zhí)行地函數(shù)設(shè)置成非 virtual 函數(shù)。
總之就是,在基類(lèi)構(gòu)造和析構(gòu)期間調(diào)用的 virtual 函數(shù)不可下降至派生類(lèi)。
10. 令 operator= 返回一個(gè) reference to *this
令賦值(assignment)操作符(=)返回一個(gè) reference to *this。
為什么這樣做呢?是因?yàn)榭梢詫?shí)現(xiàn)類(lèi)似于這樣的程序:
a?=?b?=?c?=?10;因此,我們?cè)诰帉?xiě)類(lèi)的 operator= 操作符時(shí),可以寫(xiě)成:
class?BaseClass?{public:
????BaseClass?&operator=(const?BaseClass?&rhs)?{
????????//?隨便干點(diǎn)什么
????????return?*this;?//?關(guān)鍵在于這里
????}
};
當(dāng)然啦,也可以不做返回,不過(guò)既然這是一個(gè)好的實(shí)踐,那么沒(méi)有確切的理由不去做,最好就去做。
11. 在 operator= 中處理“自我賦值”
- 確保當(dāng)對(duì)象自我賦值時(shí),operator= 有可預(yù)估的行為。其中需要注意的包括比較“來(lái)源對(duì)象”和“目標(biāo)對(duì)象”的地址、精心周到的語(yǔ)句順序以及拷貝交換。
- 確定任何函數(shù)如果操作一個(gè)以上的對(duì)象,而其中多個(gè)對(duì)象是同一個(gè)對(duì)象時(shí),其行為仍然正確。
簡(jiǎn)而言之,就是需要考慮操作符兩邊是否是同一個(gè)對(duì)象,因?yàn)槿绻峭粋€(gè)對(duì)象,會(huì)出現(xiàn)類(lèi)似下面的問(wèn)題:
class?BaseClass?{private:
????char?*data;
public:
????BaseClass?&operator=(const?BaseClass?&rhs)?{
????????delete?data;
????????data?=?rhs.data;
????????return?*this;
????}
};
int?main()?{
????BaseClass?baseClass;
????baseClass?=?baseClass;
}
自己給自己賦值,沒(méi)毛病,但是在運(yùn)算符函數(shù)的 delete data 卻帶來(lái)了問(wèn)題,因?yàn)樗鼊h除掉了自己的內(nèi)存空間,卻在下面那行 data = rhs.data 又想用了,而這時(shí)系統(tǒng)已經(jīng)收回了這塊空間,這樣一來(lái)操作系統(tǒng)肯定是不干的,所以程序就報(bào)錯(cuò)了。
那么該如何解決呢?這樣:
class?BaseClass?{private:
????char?*data;
public:
????BaseClass?&operator=(const?BaseClass?&rhs)?{
????????//?多一個(gè)檢查是否是自己的操作就可以了,也稱(chēng)證同測(cè)試
????????if?(&rhs?==?this)?return?*this;
????????delete?data;
????????data?=?rhs.data;
????????return?*this;
????}
};
12. 復(fù)制對(duì)象時(shí)勿忘其每一個(gè)成分
- 拷貝函數(shù)應(yīng)該確保復(fù)制了“對(duì)象內(nèi)的所有成員變量”以及“所有的 base class 成員”。
- 不要嘗試以某個(gè)拷貝函數(shù)去實(shí)現(xiàn)另一個(gè)拷貝函數(shù),應(yīng)該將兩者共同的部分抽取到一個(gè)新的函數(shù)中去完成,然后由兩個(gè)拷貝函數(shù)共用。
一般而言,如果自己不聲明拷貝構(gòu)造函數(shù)和拷貝賦值操作符,那么編譯器會(huì)幫自己生成的,但是!重點(diǎn)來(lái)了!如果選擇了自己去聲明定義,那么麻煩事就來(lái)了(因?yàn)榧幢憧赡艹鲥e(cuò)編譯器也不會(huì)告訴你)。
尤其是一個(gè)類(lèi)派生自基類(lèi)的時(shí)候,就需要小心謹(jǐn)慎地去處理基類(lèi)的對(duì)象,然而有些是 private 的,因此復(fù)制起來(lái)比較麻煩,這個(gè)時(shí)候可以使用這樣的方式來(lái)解決問(wèn)題:
- 對(duì)于拷貝構(gòu)造函數(shù),在初始化列表中顯式地去調(diào)用基類(lèi)的拷貝構(gòu)造函數(shù),然后在子類(lèi)的拷貝構(gòu)造函數(shù)內(nèi)部處理好自己的問(wèn)題。
- 對(duì)于賦值拷貝操作符,在合適的位置顯式調(diào)用基類(lèi)的 operator=() 函數(shù)。
具體例子見(jiàn)下面:
class?BaseClass?{private:
????string?name;
public:
????BaseClass()?=?default;
????BaseClass(int?sz,?char?c)?{?name?=?string(sz,?c);?}
????BaseClass(const?BaseClass?&rhs)?:?name(rhs.name)?{}
????BaseClass?&operator=(const?BaseClass?&rhs)?{
????????if?(this?==?&rhs)?return?*this;
????????this->name?=?rhs.name;
????????return?*this;
????}
????friend?ostream?&operator<const?BaseClass?&rhs)?{
????????os?<????????return?os;
????}
};
class?DeriveClass?:?public?BaseClass?{
private:
????int?age;
public:
????DeriveClass(int?a,?int?sz,?char?c)?:?BaseClass(sz,?c),?age(a)?{}
????//?必須要調(diào)用基類(lèi)的拷貝構(gòu)造函數(shù),否則不會(huì)拷貝構(gòu)造完全
????DeriveClass(const?DeriveClass?&rhs)?:?age(rhs.age),?BaseClass(rhs)?{}
????DeriveClass?&operator=(const?DeriveClass?&rhs)?{
????????if?(&rhs?==?this)?return?*this;
????????age?=?rhs.age;
????????//?如果不調(diào)用下面這句,將會(huì)出現(xiàn)沒(méi)有拷貝基類(lèi) name 值的問(wèn)題!
????????BaseClass::operator=(rhs);
????????return?*this;
????}
????friend?ostream?&operator<const?DeriveClass?&rhs)?{
????????os?<"\t"?<????????return?os;
????}
};
int?main()?{
????DeriveClass?d1(18,?3,?'1');
????DeriveClass?d2(20,?5,?'2');
????DeriveClass?d3(d1);
????d1?=?d2;
????cout?<endl?<endl?<????/**
?????*?正常輸出:
?????*?22222????20
?????*?22222????20
?????*?111??????18
?????*/
????/**
?????*?如果按照前兩點(diǎn)建議,那么出現(xiàn)這樣的情況概不負(fù)責(zé);
?????*?111??????20
?????*?22222????20
?????*??????????18
?????*/
}
總而言之,一旦選擇了自己去完成拷貝構(gòu)造函數(shù)和復(fù)制拷貝操作符,那么就別怪編譯器不厚道了,需要自己去謹(jǐn)慎操作。
軟考之后終于可以靜下心來(lái)看看書(shū)了?(開(kāi))?(心)
總結(jié)
以上是生活随笔為你收集整理的effective c++_【阅读笔记】Effective C++()的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python打开excel执行vba代码
- 下一篇: python多线程爬虫界面_多线程网页爬