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