條款01:視C++為一個語言聯邦 : C, Object-Oriented C++, Template C++, STL
條款02:盡量以const,enum,inline 替換#define:
1.對於單純常量,最好以const對象或enum替換#define
2.對於形似函數的宏(macros),最好改用inline函數替換#define
#define ASPECT_RATIO 1.653
const double AspectRatio = 1.653;
1)用#define定義的名字可能沒有進入記號表,編譯出錯,不方便追蹤
2)用const定義常量將得到更精簡的目標代碼(object code),因為前置處理器處理#define是會盲目的將宏名稱替換為1.653導致目標碼出現多份1.653若用const則不會出現這種問題。
當用常量替換#define有兩種特殊的情況。
1)定義常量指標。由於常量定義式通常放在標頭檔內(以便被不同源碼含入),因此有必要將指標聲明為const。
const char* const authorName = “Scott Meyer”;
string對象通常比char*-base合宜,所以authorName往往定義成這樣:const std::string authorName(“Scott Meyer”);
2)class專屬常量。為了將常量的範圍限制於class內,和確保此常量至多隻有一份實體,必須讓它成為一個static成員:
class GamePlayer {
private:
static const int NumTurns = 5;
int scores[NumTurns];
...
}
通常static & const& 整數類型(ints,chars,bools)類型成員如果不取地址,可以聲明並使用而無需定義。但如果你取某個class專屬常量的地址,或你的編譯器卻(不正確地)堅持要看到一個定義式就必須提供如下定義式
const int GamePlayer::NumTurns; //放到實現檔案,因為在聲明時已獲得初值,不能再設初值。
我們無法用#define 建立class專屬常量,因為#define不重視範圍scope。
舊時編譯器也許不支援上述文法,不允許static成員在聲明時提供初值。
則將初值放到定義式裡,但編譯器堅持必須在編譯期間知道數組score[]大小,萬一你的編譯器不允許static 整數型class常量完成 in class初值設定,可改用 the enum hack補償做法。
class GamePlayer{
private:
enum{ NumTurns=5 };
int scores[NumTurns];
}
1)取enum和#define地址一樣,不合法。如果你不想讓別人獲得一個pointer或reference指向你的某個整數常量,enum可以協助你實現這個約束。enums和#defines一樣不會導致非必要的記憶體配置。
2)實用,很多代碼用了。 enum hack是 模板元編程template metaprogramming的基礎技術。
另一個常見的#define誤用情況是用它實現宏。宏看起來像函數,但不會招致函數調用帶來的額外開銷。
#define MAX(a,b) f((a)>(b)?(a):(b))
int a =5, b = 0;
MAX(++a, b);//a被累加二次
MAX(++a, b+10);//a被累加一次
替換為內聯模板函數:宏的效率和型別安全,可預料行為
template<typename T>
inline void MAX(const T&a,const T&b)
{
f(a>b?a:b);
}
條款03: 儘可能實用const
const Rational operator* (const Rational& lhs, const Rational& rhs);//為什麼返回const對象?
Rational a, b, c;
…
(a * b) = c;//
if(a * b = c) … // 其實想做比較操作,返回const可預防此類問題
const成員函數:可被const對象調用。兩個函數如果只是常量性不同,是可以被重載。
可用mutable釋放掉non-static成員變數的bitwise constness約束(即mutable修飾的成員變數可被const成員函數修改)。
在const和non-const成員函數中避免重複:
class TextBlock {
public: …
const char& operator[] (std::size_t position) const {
…
return text[position];
}
char& operator[] (std::size_t position) { //non-const成員函數調用const成員函數避免代碼重複
return
const_cast<char&>(
static_cast<const TextBlock&>(*this)
[position]);
}
private: std::string text';
};
不能令const成員函數調用non-const成員函數,對象有可能被改動。
條款04: 確定對象被使用前已先被初始化。
1.對內建類型手工初始化
2.建構函式最好使用成員初值列 member initialization list,而不要在建構函式體內用賦值操作。初值列中列出的成員變數次序要跟在class中聲明次序一致。
3.為免除跨編譯單元之初始化次序問題,用local static 對象替換non-local static 對象
class A {
public: A(const string&);
private:
string str;
int num;
}
A::A(const string &s){
str=s; //賦值而非初始化,在初值列中調用一次default建構函式,函數體內一次賦值操作
num=0'; //內建類型不保證。
}
C++規定對象的成員變數初始化動作發生在進入建構函式體之前。如果成員變數是const或引用,則一定要初始化,而不能被賦值。
A::A(const string &s):str(s), //值調用一次copy建構函式
num(0){}
static對象:從被構造到程式結束為止。main()結束時自動析構。
包括global對象,namespace內的對象,在class內、函數內、及在file範圍內被聲明為static的對象。
local static對象:函數內的static對象
non-local static對象:其他
如果某個編譯單元內的某個non-local static對象的初始化動作使用了另一編譯單元內的某個non-local static對象,則可能未被初始化。C++對”定義於不同便一單元內的non-local static對象“的初始化次序無明確定義。
class FileSystem {
public: …
std::size_t numDisks() const;
};
extern FileSystem tfs;
//另一編譯單元
class Directory {
public:
Directory(params);
};
Directory::Directory(params) {
std::size_t disks = tfs.numDisks();
}
Directory tempDir (params);
解決方案:singleton模式,用local static對象替換non-local static對象
C++保證,函數內的local static 對象會在該函數被調用期間,首次遇上該對象定義式時被初始化。
class FileSystem {…};
FileSystem& tfs() {
static FileSystem fs;
return fs;}
class Directory {…};
Directory::Directory (params) {
std::size_t disks = tfs().numDisks();
};
Directory& tempDir() {
static Directory td;
return td;}
條款05:瞭解C++默默編寫並調用哪些函數
編譯器暗自為class建立default建構函式,copy建構函式,copy assignment操作符,以及解構函式
只有當一個類沒有定義建構函式時,編譯器才會自動產生一個預設建構函式。
如果沒有定義複製建構函式,編譯器就會合成一個,即使已經定義了其他的建構函式。
template<class T>
class NamedObject {
public: NamedObject(string &name, const T &value):nameValue(name),objectValue(value){}
private: string &nameValue;
const T objectValue;
};
string newDog(“aaa”);
string oldDod(“bbb”);
NamedObject<int> p(newDog,2);
NamedObject<int> s(oldDog, 36);
p = s; //編譯失敗
在內含引用或const成員的class內支援賦值操作符,你必須自己定義copy assignment操作符(copy建構函式內不是賦值)。
如果某個base class將copy assignment操作符聲明為private,編譯器拒絕為其derived classes產生一個copy assignment操作符。
條款06: 若不想使用編譯器自動產生的函數,就該明確拒絕。
方案:將相應成員函式宣告為private並不予實現。或使用Uncopyable這樣的base class
明確聲明一個成員函數,阻止編譯器暗自產生,另其為private,阻止外部調用。
但成員函數和friend仍然可以反問,所以只聲明而不定義它們,如不慎調用,則會得到一個串連錯誤。
將串連期錯誤移到編譯器:
class Uncopyable{
protected: Uncopyable(){} //允許derived物件建構和析構
~Uncopyablel(){}
private: Uncopyable(const Uncopyable&); //但阻止copying
Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale:private Uncopyable { …};
當嘗試拷貝HomeForSale對象時,自動產生copy建構函式和copy assignment操作符,並嘗試調用基類對於版本,但失敗。。。
條款07: 為多態基類聲明virtual解構函式
1.若果class帶有任何virtual函數,它就應該擁有一個virtual解構函式
2.class的設計目的不是作為base class使用或不是為了具備多態性,就不改聲明為virtual解構函式
C++明確指出當derived對象經過base class指標被刪除,而該base class帶有一個non-virtual解構函式,則其結果未定義的:局部銷毀。
每
一個帶有virtual函數的class都有一個相應的由函數指標構成的數組vtbl(virtual table).
當對象調用某一virtual函數,實際被調用的函數取決於該對象的vptr (virtual table
pointer)所指的那個vtbl,編譯器在其中尋找適當的函數指標。無端聲明解構函式為virtual會增加為vptr的付出的空間補償。
class SpecialString: public std::string{ //餿主意!
};
SpecialString *pss = new SpecialString(“aaa”);
std::string *ps=pss;
delete ps;//未定義!
如果企圖繼承一標準容器或任何其他帶有non-virtual解構函式的class,拒絕誘惑吧!!!
為你希望成為抽象(不能實體化)的那個class聲明一個pure virtual解構函式
class AWOV {
public: virtual ~AWOV() = 0;
};
必須提供一份定義 AWOV::AWOV() {}
不然衍生類別的解構函式調用base class的解構函式時,不然連接器會報錯。
條款08:別讓異常逃離解構函式
1.解構函式絕對不要吐出異常。如果一個被解構函式調用的函數可能突出異常,解構函式應該捕捉任何異常,然後吞下(不傳播)或結束程式。
2.如果客戶需要對某個操作函數運行期間拋出的異常做出反應,那麼class應該提供一個普通函數(而非解構函式中)執行該操作。
棧展開期間,釋放局部對象所用的記憶體並運行類類型局部對象的解構函式。在執行解構函式前如果已經有一個異常,解構函式中又拋出一個異常,將會導致調用標準庫 terminate 函數強制從程式非正常退出。
class DBConnection {
public : static DBConnection create();
void close();
};
確保DBConnection不忘記調用close()
class DBConn {
public:
~DBConn(){
db.close();}
private: DBConnection db;
};
DBConn dbc(DBConnection::create());
如果解構函式拋出異常:
1) 強迫結束
DBConn::~DBConn(){
try { db.close();}
catch(…) {
製作運轉記錄,記下對close的調用失敗
std::abort();
}
2)吞下異常
DBConn::~DBConn(){
try { db.close();}
catch(…) {
製作運轉記錄,記下對close的調用失敗
}
3)較佳的策略,客戶自己調用普通函數
class DBConn {
public:
void close(){
db.close();
closed = true;
}
~DBConn(){
if(!closed) {
try { db.close(); }
catch(…){
製作運轉記錄,記下對close的調用失敗
abort或吞下異常
}
}
private: DBConnection db;
bool closed;
};
條款10: 令operator= 返回一個reference to *this
條款11: 在operator= 中處理自我賦值
class Bitmap {…};
class Widget {
private: Bitmap *pb;
};
Widget& Widget::operator=(const Widget &rhs) {
delete pb; //沒有進行自我賦值檢測
pb = new Bitmap(*rhs.pb);//不具備異常安全,發生異常後,pb指向一塊已刪除的記憶體
return *this;
}
解決:證同測試
Widget& Widget::operator=(const Widget &rhs) {//不具備異常安全
if (this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.pb);//不具備異常安全
return *this;
}
Widget& Widget::operator=(const Widget &rhs) {
Bitmap *pOrig = pb; //異常安全,又能處理自我賦值,當自我賦值發生頻率較高時,可加入證同測試。
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
另一替換方案確保異常安全,又能處理自我賦值:copy and swap技術
class Widget{
void swap(Widget &rhs);
};
Widget& Widget::operator=(const Widget &rhs){
Widget temp(rhs);
swap(temp);
return *this;
}
Widget& Widget::operator=(const Widget rhs){ //另一較高效寫法,by-value
swap(rhs);
return *this;
}
條款12: 複製對象時勿忘其每一個成分
1.copying 函數應該確保賦值”對象內所有成員變數”及“所有base class成分”
2.不要嘗試以某個copying函數實現另一個copying函數。應該將共同機能放進第三個函數中,並由兩個函數共同調用。
為derived class編寫copying函數時,必須複製其base class成分。
class Customer {
public:
Customer(const Customer &rhs);
Customer& operator=(const Customer &rhs);
private: string name;
};
class PriorityCustomer: public Customer {
public:
priorityCustomer(const priorityCustomer &rhs);
priorityCustomer& operator=(const priorityCustomer &rhs);
private: int priority;
};
priorityCustomer::priorityCustomer(const priorityCustomer &rhs)
:Customer(rhs), //調用base class的copy建構函式,如省略,則調用預設的建構函式,基類部分為預設值
priority(rhs.priority)
{ }
priorityCustomer& priorityCustomer::operator=(const priorityCustomer &rhs) {
Customer::operator=(rhs); //可先進行證同測試,成員如有指標
priority = rhs.priority;
return *this;
}
【轉載】http://www.cnblogs.com/Atela/archive/2011/04/05/2006121.html