程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> effective C++之構造、析構、賦值運算

effective C++之構造、析構、賦值運算

編輯:C++入門知識

effective C++之構造、析構、賦值運算


條款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;

}

如果你很關心程序效率 ,可以再次把證同測試放到函數開頭,不過你有大致知道自我賦值的頻率到底有多大因為證同測試同樣需要成本
同樣我們可以通過“copy and swap”技術來替代手工排序代碼解決自我賦值的問題
class Wiget{};
void swap(Widget& rhs)
{
.......
}
Wiget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs);
swap(temp);
return *this;
}
更高效的代碼:(基於1:某class的 copy assigment操作符可以被聲明為以by value方式接受參數2:以by value方式傳遞東西會造成一份復件)
Wiget& Widget::operator=(Widget rhs)
{

swap(rhs);
return *this;
}

請記住:
@確保對象自我賦值時operator=有良好的行為,其中技術包括比較“來源對象”和“目的對象”的地址、精心周到的語句順序,以及 copy-and-swap
@確定任何對象操作一個以上的對象,而其中多個對象時同一個對象時,其行為仍然正確

條款12 賦值對象時勿忘賦值對象的每一個成分

class Date{};
class A{
public :
A(const A&);
A& operator(const A&a);
private:
string m_strname;
Date m_Date;
};
不使用編譯器提供的構造函數不要忘記復制對像的每個成分
class B:public A
{
public:
B(const B& b);
B& operator=(const B& b);
int m_p;
};

B::B(const B&b):A(B),m_p(b.m_p)//調用A的構造函數
{
logcall("copying");
}
B& B::coperator=(const B& b)
{
logcall("copying assignment");
A::operator=(B);
m_p=b.m_p;
reutrn *this;
}
讓 copy 構造函數調用 copy assignment操作符 和讓copy assignment 操作符調用 copy構造函數兩者都不會有意義!
若兩個有相近的代碼產生則通過引入第三個函數解決來降低代碼重復
請記住:
@copying 函數應該確保賦值“對象的所有成員變量”有有base class 成分
@不要嘗試以某個copying函數實現另一個copying函數,應該將共同的機能放進第三個函數中並由兩個函數共同調用


 

 

 

 

 



  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved