條款05:了解C++默默編寫了並調用了那些函數
編譯器會聲明一個copy構造函數,一個copy assignment 操作符合一個析構函數所有這些函數都是public和inline的
如果打算在一個內含reference 成員的class內支持賦值操作你必須自己定義copy assignment 操作符面對const成員的classes編譯器反應也一樣。
另一種情況:如果某個base class將assignment操作符聲明為private編譯器將拒絕為derived class 生成一個copy assignment 操作符
請記住
@編譯器可以暗自為class 創建default構造函數,copy構造函數,copy assignment 操作符,以及析構函數
條款06 如果不想使用編譯器自動生成的函數就應該明確的拒絕
有個class 用來描述待售房
class HomeForSale{};
任何一筆資產都是獨一無二的那麼該class不該存有副本
HomeForSale h1;
HomeForSale h2;
HomeForSale h3(h1)//企圖拷貝h1 ----編譯不應該通過
h1=h2;/編譯不應該通過
為解決以上問題
可以講copy構造函數和copy assgiment 操作符聲明為private來阻止編譯器暗自創建其專屬版本。但這也不是絕對的安全因為member函數和friend函數還是可以調用你的private函數。最好的方法是不去定義他們即將成員函數聲明為private而且故意不提供實現他們。
那麼HomeForSale可以這樣的改寫
class HomeForSale{
private:
HomeForSale(const HomeForSale&);//只是聲明不去實現
HomeForSale& operator=(const HomeForSale&);//同上
};
很多情況下我們專門設計一個阻止copying動作的base class像這樣
class Uncopyable{
proctected:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
其他類只需繼承此類便可獲取阻止copying行為(boost 庫提供 class noncopyable來實現阻止copying行為)
請記住:
@為駁回編譯自動(暗自)提供的機能,可將相應成員函數聲明為private並且不予以實現.使用像Uncopyable這樣的base class 也是一種做法
條款07:為多態的基類,聲明virtual析構函數
class TimeKeeper{
public:
TimeKeeper();
~TimeKeeper();
};
class AtomicClock:public TimeKerrper{...}
class WaterClock:public TimeKeeper{...}
我們這樣的到對象指針:
TimeKeeper* getTimeKeeper();//指向一個派生類動態分配的對象指針
TimeKeeper*ptk=getTimeKeeper();
delete ptk;
以上會導致一些問題C++明白的指出當derived對象經由一個base class 指針刪除時而該base class帶著一個non-virtual 析構函數那麼結果是未有定義的通常不會釋放derive的成分
消除問題很簡單為base class 提供一個virual 析構函數
但如果class 不含vitual函數那麼通常情況下他並不意圖被當做一個base class 如果令其析構函數為virtual 那麼行為也將是糟糕的因為含有virtual函數的對象其內部維護了一個vptr指針其對象將會比一般不帶virual函數的對象要大
還有一個問題需注意:
class SpecialString:public string//string有個non virual析構函數
{
};
如果你在程序中無意間將一個pointer to SpecialString轉化為一個pointer to string然後delete掉,就會出現行為不明確
string有個non virual析構函數
有時候讓class帶一個pure virtual 析構函數可能會更加的便利(同時要為此析構函數提共一份定義否則編譯器會抱怨的)
class AWOV
{
public:
virtual ~AWOV()=0;
};
AWOV::~AWOV(){}
上面這種情況只適用於帶多態性質的base class 身上
請記住:
@polymorphic(帶多態性質的)base class 應該聲明一個virtual析構函數如果class帶有任何virtual 函數,他就該擁有一個virtual析構函數.
@Classes 的設計目的如果不是作為base class 適用,或者不是為了具備多態性質就不該聲明為virual析構函數
條款08:別讓異常逃離析構函數
C++不鼓勵你在析構函數中吐出異常
如果你的析構函數必須執行一個動作而該動作可能會在失敗的時候拋出異常例如:
class DBConnection{
public:
static DBConnection Create();
void close();
}
為了 確保客戶在DBConnection對象身上調用close()一個合理的想法是創建一個用來管理BDconnection資源的class
class DBConn
{
~DBConn()
{db.close()}
private:
DBConnection db;
}
close()調用成功一切美好,但如果該調用導致了異常DBConn會將該異常傳播也就是允許他離開該析構函數那麼就會造成問題.
我們通常有一下方式改善
1 如果close拋出異常就結束程序通常用abort完成
DBConn::~DBConn()
{
try
{
db.close()
}
catch (...)
{
std::abort();
}
}
強制結束程序
2 吞下因調用close而發生的異常
DBConn::~DBConn()
{
try{ db.close(); }
catch (...)
{
..........
}
}
不管這個異常仍然繼續執行
3最佳的做法是重新設計DBConn接口使用戶有機會在對可能出現問題作出反應如下
class DBConn
{
public:
//...
void close()//供客戶使用,是客戶有一次機會捕獲異常
{
db.close();
close = true;
}
~DBConn()
{
if (!close)
{
try{ db.close(); }
catch (...)
{
//記錄異常 關閉程序或者吞下異常
}
}
}
private:
DBConnection db;
bool close();
};
請記住:
@ 析構函數絕對不要吐出異常如果一個被析構函數調用的函數可能拋出異常析構函數應該捕獲任何異常,然後吐下他或者結束程序
@如果可會需要對某個操作函數運行期間拋出異常作出反應,那麼class應該提供一個普通函數(而非在析構函數中)執行該操作
09 絕不在構造和析構過程中調用virtual函數
假如你有以下class繼承層次
class Transaction
{
public:
Transaction();
virtual void LogTransaction() const =0;//因為類型不同日志記錄也會不同
....
};
Transaction::Transaction(){
LogTransaction();
}
class BuyTransaction:public Transaction{
public:
virtual void LogTransaction()const;//交易日志Buy
}
class SellTransaction:public Transaction
{
public:
virtual void LogTransaction()const;//交易日志Sell
}
假如有以下語句 BuyTransaction那麼LogTransaction此時調用的版本將是基類中的版本並不是BuyTransaction內的版本,base class 構造期間內的virtual 函數絕對不會下降到derived class階層(在base class構造期間virtual函數不是virtual函數)
一種解決方法:在class Transaction 內將logTransaction函數改為non-virtual 函數然後要求derived class構造函數傳遞必要的信息給logTransaction構造函數
clss Transaction{
public:
explict Transaction(const std::string& loginfo);
void LogTransaction(const std::string& loginfo) const ;
};
Transaction::Transaction(const std::string& loginfo)
{
....
logTransaction(loginfo)
}
class BuyTransaction::public Transaction
{
public:
BuyTransaction(prameter):Transaction(CreateString(praneter)){...}
private:
string CreateString(parameters)
}
請記住:
@在構造函數和析構函數期間不能調用virtual函數因為這類調用從不下降至derived class
條款10:令operator=返回一個reference to *this
形如:Widget& operator=(const Widget&rhs)
{
return *this;
}
請記住:
令賦值操作符返回一個reference to *this;
條款11:在operator=中處理“自我賦值”
假如你建立一個class用來保存一個指針指向動態分配的位圖(bitmap)
class Bitmap{...}
class Widget{
private: Bitmap* pb;
}
operator=的實現代碼:
傳統方法是加入證同測試達到自我賦值的檢驗目的
Widget& Widget::operator=(const Widget& rhs)
{
if(this==&rhs)
return *this;
delete pb;
pb=new BitMap(*rhs.pb);
return *this;
}
如果你很關心程序效率 ,可以再次把證同測試放到函數開頭,不過你有大致知道自我賦值的頻率到底有多大因為證同測試同樣需要成本