读书笔记 Effective C++: 02 构造析构赋值运算
條款05:了解C++默認(rèn)編寫并調(diào)用的哪些函數(shù)
編譯器會(huì)為class創(chuàng)建:
1. default構(gòu)造函數(shù)(前提是:沒有定義任何構(gòu)造函數(shù));
如果已經(jīng)聲明了一個(gè)構(gòu)造函數(shù),編譯器就不會(huì)再創(chuàng)建default構(gòu)造函數(shù)了;
2. 析構(gòu)函數(shù)
3. copy構(gòu)造函數(shù);
對(duì)于指針,只拷貝地址,并不會(huì)重建內(nèi)容,所以要注意double free;
下面是一段錯(cuò)誤的代碼:
class TestDoubleFree
{
public:
explicit TestDoubleFree(char c)
: pTest(new char(c))
{
}
~TestDoubleFree( )
{
if(pTest != NULL) {
delete pTest;
pTest = NULL;
}
}
private:
char* pTest;
};
int main( )
{
TestDoubleFree t1('x'); //這句本身并沒有錯(cuò)
TestDoubleFree t2(t1); //但是這句錯(cuò)了,會(huì)引起double free
}
這是一段錯(cuò)誤的代碼,運(yùn)行時(shí)會(huì)發(fā)生double free。main函數(shù)中,如果單獨(dú)使用TestDoubleFree t1('x'),這并不會(huì)有問題。
問題在于TestDoubleFree t2(t1),copy構(gòu)造函數(shù)會(huì)讓t1.pTest和t2.pTest同時(shí)指向一塊內(nèi)存單元,那么t1,t2并析構(gòu)的時(shí)候,就會(huì)對(duì)該內(nèi)存單元發(fā)生double free。
解決這個(gè)問題:
1. 可以繼承boost::noncopyable,這樣就不允許使用TestDoubleFree t2(t1)語句。(例子見條款06)
2. 可以用boost::share_ptr替代裸指針;
#include <boost/shared_ptr.hpp>
class TestDoubleFree
{
public:
explicit TestDoubleFree(char c)
: pTest(new char(c))
{
}
~TestDoubleFree( )
{
}
private:
boost::shared_ptr<char> pTest;
};
int main( )
{
TestDoubleFree t1('x');
TestDoubleFree t2(t1);
}
4. copy assignment操作符;
copy構(gòu)造函數(shù)一定會(huì)生成,但是copy assignment賦值符并不一定能默認(rèn)生成。
如果class中含有reference成員變量,const成員變量,那么賦值運(yùn)算無法完成;
#include <cassert>
class TestNonCopyAssignment
{
public:
TestNonCopyAssignment(char c, int i)
: ch(c), cInt(i)
{
}
void setChar(char c)
{
ch = c;
}
void testAssert(char c, int i)
{
assert(ch==c);
assert(cInt==i);
}
private:
char& ch;
const int cInt;
};
int main( )
{
TestNonCopyAssignment t1('x', 123);
TestNonCopyAssignment t2(t1);
t1.setChar('z');
t1.testAssert('z', 123);
t2.testAssert('z', 123);
t2 = t1; //賦值符號(hào)錯(cuò)誤
}
由于TestNonCopyAssignment類含有reference和const,operator=被禁用,如果強(qiáng)行賦值,編譯器會(huì)報(bào)錯(cuò):
錯(cuò)誤: 使用了被刪除的函數(shù)‘TestDoubleFree& TestDoubleFree::operator=(const TestDoubleFree&)’
錯(cuò)誤: ‘TestDoubleFree& TestDoubleFree::operator=(const TestDoubleFree&)’ is implicitly deleted because the default definition would be ill-formed:
錯(cuò)誤: non-static reference member ‘char& TestDoubleFree::pTest’, can’t use default assignment operator
錯(cuò)誤: non-static const member ‘const int TestDoubleFree::count’, can’t use default assignment operator
?
條款06:若不想使用編譯器自動(dòng)生成的函數(shù),就應(yīng)該明確拒絕
方法1:
在private中聲明而不定義:copy構(gòu)造函數(shù),copy assignment操作符;
缺點(diǎn):friend函數(shù)或者成員函數(shù)調(diào)用,這個(gè)錯(cuò)誤不是出現(xiàn)再編譯期,而是link期;
方法2:
繼承boost::noncopyable類;
缺點(diǎn):多重繼承可能會(huì)阻止empty base class optimization
優(yōu)點(diǎn):醒目地標(biāo)識(shí)該類不能被copy
實(shí)現(xiàn)條款05的例子:
#include <boost/noncopyable.hpp>
class TestDoubleFree : private boost::noncopyable
{
public:
explicit TestDoubleFree(char c)
: pTest(new char(c))
{
}
~TestDoubleFree( )
{
if(pTest != NULL) {
delete pTest;
pTest = NULL;
}
}
private:
char* pTest;
};
int main( )
{
TestDoubleFree t1('x');
TestDoubleFree t2(t1); //由于繼承boost::noncopyable, 所以編譯不會(huì)通過
}
?
條款07:為多態(tài)基類聲明virtual析構(gòu)函數(shù)
1. 具有polymorphic(多態(tài))的base class,或者帶有任何virtual函數(shù)的class,應(yīng)該聲明virtual析構(gòu)函數(shù);
避免局部析構(gòu);
2. 如果class設(shè)計(jì)的目的就不是作base class使用,或者不是為了具備多態(tài),就不應(yīng)該聲明virtual析構(gòu)函數(shù)。
節(jié)省存儲(chǔ)空間;
3. 有時(shí)候?yàn)榱说玫揭粋€(gè)抽象類,以避免該類被實(shí)例化,就把析構(gòu)函數(shù)定義為pure virtual,但是必須為這個(gè)pure virtual析構(gòu)函數(shù)提供一份定義,要不然link會(huì)出錯(cuò)的。
#include <iostream>
class Base
{
public:
virtual void print( )
{
std::cout << "Base Classes." << std::endl;
}
virtual ~Base( )=0;
};
// 析構(gòu)函數(shù)先聲明成pure virtual再定義
// 1. 如果不在外面定義,link會(huì)出錯(cuò)的
// 2. 這是一個(gè)小技巧,能阻止Base被實(shí)例化
Base::~Base( )
{
}
class DerivedA : public Base
{
};
class DerivedB : public Base
{
public:
void print( )
{
std::cout << "Derived Classes." << std::endl;
}
};
int main( )
{
Base* p = new DerivedA( );
p->print( );
delete p;
p = NULL;
p = new DerivedB( );
p->print( );
//由于p是Base*,所以如果Base的析構(gòu)函數(shù)不是virtual的,就可能會(huì)導(dǎo)致Base的那部分析構(gòu)了而Derived的那部分沒能被析構(gòu)
delete p;
p = NULL;
}
?
條款08:別讓異常逃離析構(gòu)函數(shù)
1. 析構(gòu)函數(shù)絕對(duì)不要吐出異常。
如果某個(gè)語句在運(yùn)行期間可能拋出異常,class應(yīng)該提供普通函數(shù)執(zhí)行該操作,而且該函數(shù)需要在析構(gòu)函數(shù)之前被用戶手動(dòng)調(diào)用,而析構(gòu)函數(shù)中包含這個(gè)普通函數(shù)只是起到雙重保險(xiǎn)的作用。
class DBConn
{
public:
//提供給客戶使用,異常由客戶處理
//這里談?wù)摰氖莇b.close()吐出異常的處理,不是db.close()關(guān)閉失敗的處理,關(guān)閉失敗是db.close()程序本身的錯(cuò)誤
void close(){
db.close();
closed = true;
}
?
~DBConn(){
//如果客戶沒有處理,這里將代為處理,只是起到雙保險(xiǎn)的作用;但是原則上,是建議客戶程序員處理。
if(!closed){
try{
db.close();
}catch(...){
// 默認(rèn)的處理一般是直接結(jié)束程序,以避免錯(cuò)誤傳播
}
}
}
private:
DB db;
bool closed;
};
?
條款09:絕不在構(gòu)造和析構(gòu)過程中調(diào)用virtual函數(shù)
如果class中含有virtual函數(shù),在構(gòu)造函數(shù)中調(diào)用該函數(shù),而此時(shí)派生類還沒能創(chuàng)建,調(diào)用virtual函數(shù)不能正確指向派生類的函數(shù)。
析構(gòu)函數(shù)也一樣,派生類先析構(gòu),基類的析構(gòu)函數(shù)調(diào)用virtual函數(shù),此時(shí)vpt指向的函數(shù)已經(jīng)析構(gòu)了。
#include <iostream>
class Base
{
public:
Base( )
{
// 此時(shí)Derived Class還沒有構(gòu)建出來,所以調(diào)用的是Base Classes的print函數(shù)
print("Constructor");
}
virtual ~Base( )
{
// 此時(shí)Derived Class已經(jīng)析構(gòu),所以調(diào)用的是Base Classes的print函數(shù)
print("Destructor");
}
virtual void print(std::string str)
{
std::cout << "Base Classes: " << str << std::endl;
}
};
class Derived : public Base
{
public:
Derived( )
{
print("Constructor");
}
~Derived( )
{
print("Destructor");
}
void print(std::string str)
{
std::cout << "Derived Classes: " << str << std::endl;
}
};
int main( )
{
Derived dd;
}
運(yùn)行結(jié)果:
Base Classes: Constructor
Derived Classes: Constructor
Derived Classes: Destructor
Base Classes: Destructor
?
條款10:令operator=返回一個(gè)reference to *this
class Widget{
public:
// 也適用于+=,-=, *=
Widget& operator=(const Widget&){
......
return *this;
}
};
這樣做的好處是可以連續(xù)賦值;
如:x = y = z = 15;
會(huì)被正確的解析為:x = (y = (z = 15));
?
條款11:在operator=中處理“自我賦值”
版本1:
Widget& Widget::operator=(const Widget& rhs){
if(this == &rhs)return *this;
delete pb;
try{
pb = new Bitmap(*rhs.pb);
}catch(...){
}
return *this;
}
版本1的缺點(diǎn)是不具備異常安全性:如果new失敗了(內(nèi)存不足或者Bitmap的copy構(gòu)造函數(shù)出現(xiàn)異常),而舊的pb又已經(jīng)delete了,這個(gè)對(duì)象就成了一顆地雷(一踩就崩;即使不踩,析構(gòu)它也可能會(huì)因?yàn)閐elete pb而崩;就算使用pb之前判斷pb!=NULL,程序也會(huì)進(jìn)入不穩(wěn)定的狀態(tài))。
版本2:
Widget& Widget::operator=(const Widget& rhs){
if(this == &rhs)return *this; //可加可不加,需要權(quán)衡。加了,如果自我賦值少,影響效率;不加,如果自我賦值多,由于后面要重建,也會(huì)影響效率。
Bitmap* pOrig = pb;
try{
pb = new Bitmap(*rhs.pb);
delete pOrig;
}catch(...){
throw "賦值失敗了";
}
return *this;
}
版本2可以解決異常安全性,如果new失敗了,此時(shí)pb依然指向原來的對(duì)象,這樣程序還是可以繼續(xù)運(yùn)行的,只是進(jìn)入賦值失敗的異常分支,而不是像版本1那樣只能退出了。
版本3:
class Widget{
void swap(Widget& rhs);
Widget& Widget::operator=(const Widget& rhs){
Widget tmp(rhs);
swap(tmp);
return *this;
}
};
使用copy and swap技術(shù),能有效處理異常安全性。
版本4:
把版本3的operator=改為value傳遞
Widget& Widget::operator=(Widget rhs){
swap(rhs);
return *this;
}
版本4是版本3的簡(jiǎn)化版,而且可能會(huì)讓編譯器生成更高效的代碼,但是犧牲了清晰性。
?
完整代碼:
#include <iostream>
#include <exception>
class BitMap
{
public:
explicit BitMap(int ibm)
: ibm(ibm)
{
}
BitMap(const BitMap& bm)
: ibm(bm.ibm)
{
throw std::string("Test Exception");
}
int get( )
{
return ibm;
}
private:
int ibm;
};
class Widget
{
public:
explicit Widget(int ibm)
: pb(new BitMap(ibm))
{
}
~ Widget( )
{
delete pb;
}
Widget(const Widget& wg)
: pb(new BitMap(*wg.pb))
{
}
Widget& operator+=(const Widget& wg)
{
Widget tmp(wg);
std::swap(tmp.pb, pb);
return *this;
}
Widget& operator-=(Widget wg)
{
std::swap(wg.pb, pb);
return *this;
}
Widget& operator*=(const Widget& wg)
{
if(this == &wg) return *this;
BitMap* pOrig = pb;
pb = new BitMap(*wg.pb);
delete pOrig;
return *this;
}
// 錯(cuò)誤的代碼
Widget& operator/=(const Widget& wg)
{
if(this == &wg) return *this;
delete pb;
pb = new BitMap(*wg.pb);
return *this;
}
void print( )
{
std::cout << pb->get( ) << std::endl;
}
private:
BitMap* pb;
};
int main( )
{
Widget wg1(1);
Widget wg2(2);
wg1.print( );
wg2.print( );
try {
wg1 += wg2;
} catch(std::string& str) {
//賦值失敗,但是原來的值沒有修改
std::cout << str << std::endl;
wg1.print( );
wg2.print( );
}
try {
wg1 -= wg2;
} catch(std::string& str) {
//賦值失敗,但是原來的值沒有修改
std::cout << str << std::endl;
wg1.print( );
wg2.print( );
}
try {
wg1 *= wg2;
} catch(std::string& str) {
//賦值失敗,但是原來的值沒有修改
std::cout << str << std::endl;
wg1.print( );
wg2.print( );
}
try {
wg1 /= wg2;
} catch(std::string& str) {
//賦值失敗,原來的值卻成了個(gè)地雷
//不僅狀態(tài)不穩(wěn)定,而且會(huì)造成內(nèi)存泄漏
std::cout << str << std::endl;
wg1.print( );
wg2.print( );
}
}
?
條款12:復(fù)制對(duì)象時(shí)勿忘其每一個(gè)成分
1. 不要copy構(gòu)造函數(shù)和copy assignment函數(shù)應(yīng)該確保copy“對(duì)象中的所有成員變量”,以及base classes成分
2.?不要用copy構(gòu)造函數(shù)去實(shí)現(xiàn)copy assignment函數(shù),也不要用copy assignment函數(shù)去實(shí)現(xiàn)copy構(gòu)造函數(shù);
copy構(gòu)造函數(shù)是,創(chuàng)建并初始化一個(gè)新的對(duì)象,copy assignment函數(shù)是,在已初始化的對(duì)象上做處理;
如果兩者有相同的代碼,可以抽出來定義private函數(shù);
class PriorityCustomer : public Customer{
public:
PriorityCustomer(const PriorityCustomer& rhs) : Custormer(rhs), priority(rhs.priority){
}
PriorityCustomer& operator=(const PriorityCustomer& rhs){
Customer::operator=(rhs);
priority = rhs.priority;
return *this;
}
private:
int priority;
};
例:?
#include <iostream>
#include <sstream>
#include <cassert>
class Base
{
public:
Base(int a, int b, int c)
: a(a), b(b), c(c)
{
}
protected:
int toInt( )
{
int tmp;
std::stringstream ss;
ss << a << b << c;
ss >> tmp;
return tmp;
}
private:
int a;
int b;
int c;
};
class Derived : public Base
{
public:
Derived( )
: Base(1, 1, 1), d(1), e(1)
{
}
Derived(int a, int b, int c, int d, int e)
: Base(a, b, c), d(d), e(e)
{
}
Derived(const Derived& derived)
: Base(derived), d(derived.d), e(derived.e)
{
}
Derived& operator=(const Derived& derived)
{
Base::operator=(derived);
d = derived.d;
e = derived.e;
return *this;
}
int toInt( )
{
int tmp;
std::stringstream ss;
ss << Base::toInt( ) << d << e;
ss >> tmp;
return tmp;
}
private:
int d;
int e;
};
int main( )
{
Derived dd(1, 2, 3, 4, 5);
assert(dd.toInt( ) ==12345);
Derived ee(dd);
assert(ee.toInt( ) ==12345);
Derived ff;
assert(ff.toInt( ) ==11111);
ff = ee;
assert(ff.toInt( ) ==12345);
}
轉(zhuǎn)載于:https://www.cnblogs.com/leagem/archive/2013/03/20/2972200.html
總結(jié)
以上是生活随笔為你收集整理的读书笔记 Effective C++: 02 构造析构赋值运算的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 字符串加密和解密
- 下一篇: java与c++的区别-转