《effective c++》第一遍簡記

1.將c++視爲一個語言組
次語言 (1)c (2)Object-Oriented C++ (3)Template C++ (4)STL
c++高效編程守則視狀況而變化,取決於你使用c++的哪一部分
2.儘量以const,enum,inline替換#define
#define定義的記號在編譯器處理源碼之前就被預處理器移走了,可能並未進入symbol table
例   #define ASPECT_RATIO 1.653
   改爲 const double AspectRatio = 1.653;
使用常量可能比#define有更小的碼量,#define會導致多個1.653,const常量就不會
const替換#define時的特殊情況:
(1)寫兩次const保證指針和指向的內容均不可更改
const char* const authorName = "liupeng";
string往往比char更合適些
const std::string authorName("liupeng");
(2)class的專屬常量,確保常量最多一份,加static
class face{
static const int Myeyenum = 2;//將其在class中聲明之後,若不取它的地址,無須提供定義式,把這個定義式放在實現文件裏
}
  已經賦初值了,可以定義如下:
const int face::Myeyenum;                                                                     類內static必須要是常量才能賦初值,即加上const

在編譯器不允許的情況下可以使用enum來初始化常量,如下
class GamePlayer { 
enum {Myeyenum = 2};
……
};
       enum類型不允許獲得地址,類似#define
                                  
#define 實現宏函數時,將每個參數加上小括號,但仍然會有錯誤發生
此時可以使用template inline來替換
template<typename T>
inline void manage T(const T& a, const T& b)
  {
a>b?a:b;
}

#ifdef/#ifndef在定義頭文件時使用
請記住
單純常量,最好以const和enum替換#define
形似函數的宏,使用inline替代#define
3.儘可能使用const
const出現在*號左邊,表示被指的是常量,出現在*號右邊表示指針是常量。
用於STL迭代器時
const std::vector<int>::iterator iter = vec.begin();//表示指針不能變
std::vector<int>::const iterator iter = vec.begin();//表示指向內容不能變


加與不加const(最後)的函數算重載,可以實現接受相同參數,返回值不同的兩個函數

利用mutable可以修改const的約束,被mutable標記的member不受const的限定,可以在函數中修改這些member

轉型 cast操作
請記住:
const與non-const有實質等價實現時,令non-cosnt調用const可避免重複代碼,即在返回值加上轉除操作
4.確定對象被使用前以初始化:
若在初始化列表中需要初始化的數據太多,可以把一些內置變量移到函數內用賦值完成,一般放在一個函數中(private),供所有構造函數調用。
最好初始化列表次序與聲明次序相同

c++對於"定義於不同編譯單元內的non-local staic對象"的初始化次序無明確要求,爲了消除未初始化就使用的隱患,將每個non-local static 對象搬到自己的專屬函數(對象在函數內被聲明爲static),函數返回reference指向object。即用local static代替non-local static。如下:
Myclass tfs(){
static Myclass mc;
return mc;
}
一個函數內就做了返回一個local Myclass的工作,直接用tfs().成員即可。


任何一種static對象,在多線程等待下都會有麻煩,處理麻煩的一種做法:單線程啓動階段,手動調用reference-return函數,可以消除與初始有關的race conditions。


請記住:
內置型對象手工初始化
構造函數用member initialization list,排列次序與class中聲明次序相同,不要在ctor中使用assignment。
使用上述方法以local static替換non-local static對象。
5.瞭解c++默認編寫了哪些調用函數
當有成員是string*時,必須自己編寫複製構造函數。
6.若不使用編譯器自動生成的函數,則明確拒絕
每個object僅有一次的情況下,想拒絕構造函數和copy ctor,需要將其copy與copy assignment操作符聲明爲private且不定義。
另一種可行的辦法是實現專門阻止copy的class:
class Uncopyable{
protected:
Uncopyable();
~Uncopyable();
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
接下來僅需要private繼承即可:
class Myclass:private Uncopyable{……};
7.爲多態基類聲明virtual析構函數
基類指針指向derived class時,同過base point 刪除 derived class時,若base class的dtor不是virtual,未調用derived dtor,會造成delete掉了base部分,而derived部分還在。
正確的做法是將base class的dtor聲明爲virtual。


若需要一個純虛函數作爲基類,可以聲明一個pure virtual的析構函數,並未這個純虛dtor加上定義(純虛函數是可以有定義的,一般爲空,無定義析構的時候無法調用)。


請記住:
多態的base class應將dtor聲明爲virtal
若不是作爲base class繼承用,則不需要聲明爲virtual函數。
8.別讓異常逃離析構函數
c++並不鼓勵讓dtor吐出異常。一個class vector,若在第一個元素的dtor吐出異常,其他九個還應被銷燬,若第二個還吐出,那麼程序很可能就中止了。
若close拋出異常就結束程序,用abort:
DBConn::~DBConn()
{
try{
db.close();
}
catch(……){
std::abort();
}
}
還可以吞下因調用close引發的異常
DBConn::~DBConn()
{
try{
db.close();
}
catch{...}{
記錄下close調用失敗的信息;
}
}
因此修改後的函數爲:
class DBConn{
public:
...
void close()
{
db.close();
closed = true;
}//這個close是給客戶,讓他決定是否調用close
~DBConn()
{
if(!closed){
try{
db.close();
}
catch(...){
記錄信息
}
}
}//若此處出現問題,客戶沒理由抱怨,畢竟他曾經可以第一手處理問題。
private:
DBConnection db;
bool closed;
}
 
請記住:
析構函數絕對不要吐出異常,若拋出異常,則應吞下它們或結束程序。
若客戶需要對某func運行期間的異常做出反應,則class應提供一個函數(非dtor)執行該操作
9.不在dtor和ctor中調用virtual函數
構造是多態不生效,若生效,base class的point調用derived的virtual時,derived的data member還未初始化成功,得到的結果是未定義的。析構時同樣,多態生效,derived已經析構了,引起未知操作。****與此同時,base的ctor調用base的virtual,derived調用derived的virtual function。****

爲了在ctor中調用正確的函數,一種:把base中的virtual改爲non-virtual,然後由derived class傳遞必要的信息給base的ctor,例子如下:
class baseclass{
public:
explicit baseclass(string &loginfo);
void func(string &loginfo);
};
baseclass::baseclass(string &loginfo)
{
func(loginfo);
}
class derivedclass:public baseclass{
public:
derivedclass(parameters):baseclass(func(parameters)){...}
private:
static std::string func(parameters);
};
如上,derivedclass中的static func是作爲輔助函數(根據需要看是否爲static),通過parameters計算傳給baseclass的值。
10.令operator= 返回一個reference to *this
適合連續賦值
11.在operator中處理“自我賦值”
常見的處理自我賦值的方法:
在operator= 最前加上證同測試:
if(this == &classobject)
return *this;


處理異常安全往往就處理掉了自我賦值問題:
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap* pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
如上,new時異常,pb與rhs中的pb仍不變,而且能夠處理自我賦值問題。


替代方案copy and swap技術(不推薦):
Widget& Widget::operator=( Widget rhs)
{
swap(rhs);
return *this;
}
參數傳入一個副本,再交換數據。但有些情況不適合(如有data member爲指針時)
12.複製對象時別遺漏成分
copy和copy assignment不能相互調用,若二者有重複代碼,那就建立一個新的member function給二者調用,往往是private的,叫做init。
*記住:
確保複製所有data member和base class成分。 
13.以對象管理資源:
將資源放進對象裏,確保被釋放。auto_ptr就是一個類指針對象。
void (){
std::auto_ptr<classtype> pclass(createobject());
}
調用了factory函數產生object,auto_ptr的析構函數自動刪除pclass。
思路:(1)獲得資源後立刻放進管理對象
(2)管理對象用析構函數保證資源釋放。
爲防止多個指向同一個,在賦值或複製時,複製所得的指針將取得唯一的資源擁有權,原來的變爲null。

shared_ptr,會跟蹤有幾個對象指向這個資源,若沒有,則刪除(在函數結束變量消失,會自動刪除資源),它的複製行爲比auto_ptr正常多了。
***auto_ptr和shared_ptr在其析構中做delete而不是delete[],因此,分配數組指針給它倆是不行的。
14.在資源管理類中小心coping行爲
若一個RAII對象被複制,可以:(1)禁止複製,將copy聲明爲private
(2)對底層資源使用引用計數,內含一各std::trl::shared_ptr
(3)複製底部資源,即深度複製
(4)轉移底部資源擁有權
*記住:
複製RAII時一併複製它所管理的資源
常見:禁止copy、引用計數等。
爲了避免忘掉釋放,建立一個類來管理,析構函數裏寫釋放
15.在資源管理類中提供對資源的訪問
可以寫一個轉換函數,顯示返回所需資源的指針
*記住:
APIs需要訪問原始資源,需提供一個方法
對原始資源訪問,顯式轉換比較安全,隱式較方便
16.成對使用new和delete時要採取的相同形式
delete需知道刪除數組時的大小,在內存中有記錄,加上[]後,它會去尋找。
17.以獨立語句將newed object放入智能指針。
即在給智能指針賦值的()中不要添加其他函數,因爲一旦其他函數異常,則資源泄露。
******設計與聲明******
18.讓接口容易被正確使用,不易被誤用
在接口上使用自己定義的類,並在類的構造函數上用explicit聲明(防止隱式類型轉換)。
???
19.設計class如設計type
設計class往往面臨以下問題:
(1)新type如何被創建和銷燬
(2)對象的初始化和賦值有什麼差別
(3)type被passed by value,會發生什麼?
(4)type的“合法值”
(5)type需要配合某個繼承圖系嘛?
……
20.以pass by reference to const 替換pass by value
以reference爲函參時,不會出現derived class被切割的情況。
reference實現時由point完成的,所以對於內置類型,by value的效率要比by reference高些。
*注意:
以上並不適用於STL的迭代器和函數對象,pass by balue 往往比較適當
21.必須返回對象時,別妄想返回其reference
返回函數內的local object時用reference是錯誤的,函數結束會銷燬object。可以new一個用來返回的reference。

若必須返回一個對象(比如operator *),那就返回一個對象。
22.將成員變量聲明爲private
只有聲明爲private,在以後修改代碼的時候才能做最少的修改
23.寧以non-member、non-friend替換member函數
non-member 與non-friend function有更大的彈性
c++中自然的做法,將clearBrowser成爲一個non-member函數並位於WebBrowser所在的同一個namespace內。
將不同用途的類和函數放到不同的頭文件中,並用namespace封裝。功能不同,但是一個類下的,放到不同頭文件中用一個namespace包含。
24.若所有參數皆需類型轉換,請爲此採用non-member函數
如果是member函數,例如*的情況下,會有一種情況不能轉換,因此改爲non-member函數(參數要配合類型轉換才能用)或public中的函數。
可以避免friend函數就避免
25.寫一個不拋出異常的swap函數
swap是個模板函數,需要類型T支持copying。
對於member帶指針的class,可以將swap特殊化並作爲class的member function:
namespace std {
void swap< Class<T> > (Class<T>& a,Class<T>& b){
a.swap(b);
}
}
這樣子不行,c++不允許對function進行特化,那麼用模板重載,也不行,std不允許添加新的templates到std裏頭。
因此聲明一個non-member swap讓他調用member swap,如下:
namespace std{
template<typename>
void swap(Widget<T>& a,Widget<T>& b)//這裏是添加一個重載版本的swap
{ a.swap(b);}
}
提供一個public swap成員,讓他高效置換。
在class或template所在命名空間提供一個non-member swap,並令它調用上述swap成員函數
爲class特化std::swap,並令它調用swap函數。
若用到swap,請使用using聲明,讓std::swap對函數內部可見
記住:
std::swap對你的類型效率低時,提供一個swap函數,並確定這個函數不拋出異常
若提供一個member swap,也應提供給一個non-member swap(當然參數可能不同)來調用前者,對於classes,特化std::swap
千萬不要在namespace std中加入全新東西
實現:
26.儘可能延後變量定義式的出現時間
避免因爲沒用到引起的構造操作,另外,儘量在定義時就給它賦初值,避免無意義的default構造調用。
在循環內部儘量避免定義類型,或許考慮賦值與構造+析構的代價再做決定更好。
27.儘量少做轉型操作 casting
c++提供四種轉型:
const_cast<T>(expression)//移除對象常量性,如const、volatile(確保本指令不會被優化)
dynamic_cast<T>(expression)//類的上下行轉換,執行下行時由類型檢查功能,比static_cast更安全,另外還支持交叉轉換
reinterpret_cast<T>(expression)//安全性不高,可以用於多種轉換
static_cast<T>(expression)//強迫隱式轉換,類指針的上行轉換
大範圍的轉型會導致很低的運行速度。


替代品有:1.將基類指針存到容器中,用於指向不同的derived class.
 2.利用virtual function,實現基類指針調用不同函數
記住:
如果可以,避免轉型操作
轉型是必要的,可以隱藏在某個函數後面
寧可用你c++z轉型,不用舊式轉型
28.避免返回handles執行對象的內部成分
references、point、迭代器都是handles,它們作爲返回值時可能會被用來修改內部數據 ,破壞了封裝。需要對返回類型加上const。
另外若指向內部成分,有可能對象被銷燬了(作爲返回值),指針就變成dangling point了。
oeprator[]允許返回handle
記住:
避免返回的handles指向對象內部。
29.爲“異常安全”而努力是值得的
一個軟件系統要麼有異常安全,要麼全盤否定
當異常被拋出是,帶異常安全的函數會:
不泄露任何資源
不允許數據損壞
        *****將可能導致異常的語句儘可能地延後*****
異常安全函數提供三個保證之一:
若異常被拋出,程序內任何事物仍保持在有效狀態下。
若異常被拋出,程序狀態不改變
承諾絕不拋出異常

另外copy and swap是個不錯的處理異常的方法
爲打算修改的建一個副本,然後在副本上做必要的修改,若拋出異常,原對象不變,待修改後再做swap操作。

記住:
強力保證異常安全往往能夠 用copy and swap 實現出來,但並非所有都有實現意義(如果佔過多資源)。
函數異常安全性等於其內部調用函數異常安全性最低的那一個。
30.瞭解inline的裏裏外外
慎重使用inline,將它用在小型、頻繁的函數上。
不要將function templates在頭文件,就聲明爲inline,因爲function templates往往要到運行期才能確定具像化爲什麼function
31.將頭文件的編譯依存關係降到最低
能用object references 或object pointers完成,就不使用object。
儘量以class聲明式替換class定義式
記住:
依賴聲明式而不是定義式(定義式即需要知道大小),手段:handle calsses和interface classes
程序庫頭文件應僅有聲明式
32.確定public繼承的is-a關係
公有繼承。
33.避免遮掩繼承來的名稱
繼承後virtual會覆蓋原有的function
34.區分接口繼承和實現繼承
public繼承分爲:函數接口繼承、函數實現
記住:
derived classes總是繼承base class的接口
pure virtual 函數只具體指定接口繼承
***簡譜的impure virtual函數指定接口及缺省實現繼承***
non-virtual函數具體制定接口繼承及強制性實現繼承
35.考慮virtual函數以外的其他選擇**********
base class中virtual未聲明爲pure時,應該提供一個default function
***non-virtual interface實現template method模式:
在public中定義一個non-virtual函數,調用private中定義的virtual函數做實際的工作。
non-virtual叫做virtual的外覆器(wrapper)
wrapper在virtual function運行前後可以做一些準備、善後工作
***function pointers 實現的strategy模式
定義一個函數讓ctor接受一個指針,指向需要的函數
(1)不同實體可以有不同的目標函數
(2)函數可在運行期變更
***由tr1::function完成Strategy模式
**function**
類模板,類的成員變量是一個函數指針
void foo(int i) {cout<<"jj01"<<endl;}
void (*p)(int) = foo;
int main(){
tr1::function<void (int)> fm;
fm = foo;
(*p)(2);
fm(2);
return 0;
}
它類似於函數指針,但有函數指針做不到的地方,
(1)定義一個function對象
(2)像函數指針一樣,這個指針需要指向某個函數
(3)function重載了()符號,用着能好用些
較函指優點:
能夠指向static、外部函數
類的非static成員函數(需指明是哪個對象的成員函數)
綁定虛函數,不改變多態
**         **
private中內含一個函數指針,指向實際操作的函數,可以兼容更多。
***古典的strategy模式***
在另一個繼承體系中實現需調用的函數。
以上是替代virtual的幾種方案,當然還有更多,根據實際需要設計。
記住:
將調用的函數從member function移到class外部,會導致無法訪問non-public成員。
36.不重新定義繼承而來的non-virtual函數
靜態綁定的函數,要調用只和定義的類型有關,不同於多態的靜態綁定。
37.不重定義繼承而來的缺省參數值
non-virtual上一條已經說過
virtual的情況:virtual是動態綁定,缺省參數是靜態綁定
· 即可能會引發由base point指向derived class時,調用了base class對應函數的參數,顯然不對。(這裏的描述可能需要理解後才能看懂)
38.符合塑模出has-a或根據某物實現出
複合與繼承是不同的兩個概念
39.謹慎使用private繼承
private意味着實現部分被繼承,接口部分被略去,是一種實現技術
private可以造成empty base最優化(極端情況)。
40.謹慎使用多重繼承
virtual繼承有額外的維護成本
???
41.瞭解隱式接口和編譯器多態
template<typename T>
void doProcessing(T& w)
{
if(w.size()>10 && w != someNastyWidget){
T temp(w);
temp.normalize();
temp.swap(w);
}
}
如上:w必須支持哪種接口由template中執行的操作決定。
凡涉及w的任何函數調用,有可能造成template具現化function,不同參數,template的function不同,叫編譯期多態。
顯示接口由簽名式構成,隱式接口(如上):(1)提供一個名爲size的成員函數,該函數返回一個整數值。(2)必須支持一個operator!=函數來比較兩個T函數  但事實是可能不需要,可以藉由隱式轉換或繼承來擺平,叫隱式接口。
記住:
templates多態通過template具現化和函數重載解析,發生在編譯期
42.瞭解typename的雙重意義:
在template中class與typename並不一直有相同的意義 
在template中設計一個嵌套叢書類型名稱,就必須在它前加上typename告訴編譯器,它是個名稱,不是一個成員。但在base classes list與member initialization list中不可以修飾base class。
43.學習處理模板化基類內的名稱
在繼承模板類時,編譯器並不知道derived class繼承的是哪個函數,需要在具現化後才能確定。
使用this->調用函數
使用using聲明式,表明調用的是那一個模板裏的函數
using msgsender :public msgsender<company>::sendClear
記住:
模板類繼承,之後調用函數,通過一個this->指涉base class templates內的成員,。
44.將與參數無關的代碼抽離
non-template中重複的十分明確,而在template中,具現化多次可能發生重複。????????
記住:
templates生成多個classes和多個函數,所以任何template代碼都不該 與某個造成膨脹的template參數產生相依關係
非類型模板參數造成的代碼膨脹,往往以函數參數或class成員變量替換template參數。
因類型參數造成的代碼膨脹,可以讓有相同二進制表述的具現類型共享實現碼
45.運用成員函數模板接受所有兼容類型
構造和賦值函數可以寫爲模板用來進行類型轉換。
複製一個auto_ptr&時,它其實被改動了。
記住:
聲明member template用於泛化copy和assignment,仍需要聲明正常的copy和assignment
46.需要類型轉換時,請爲模板定義非成員函數。
記住:
編寫一個class template,它提供所有參數的隱式類型轉換,請將那些函數定義爲class template內部的friend函數。
47.請使用traits classes表現類型信息
traits class使類型相關信息在編譯器可用,以template和template特化實現。
重載可使traits classes在編譯器對類型執行if else測試。
48.認識template元編程
TMP,執行與c++編譯器內的程序,編譯期會長,但文件,運行,內存可能會相應減少。
49.瞭解new-handler的行爲
new內存不足時,拋出異常,但可以設置拋出異常時調用的函數,<new>中的set_new_handler用來設置回調函數,定義如下
namespace std{
typedef void(*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
使用:
void outofmem(){std::cout<<"memory lose"; std::abort();}
int main(){
std::set_new_handler(outofmem);

}
static成員必須在class中(除非他們是consnt)
    記住:
set_new_handler允許客戶指定一個函數,在內存分配無法獲得滿足時被調用。
50.瞭解new和delete的合理替換時機
 在new是可能會面臨alignment(malloc就是在這個約束)的問題,
   何時在全局性與class專屬的基礎上合理替換缺省的new和delete:
爲了檢測錯誤
手機動態分配內存之使用統計信息
降低內存管理帶來的空間開銷
。。。
51.編寫new和delete時需固守常規
??
記住:
operator new應內含一個無窮循環,在其中嘗試分配內存,無法滿足,調用new-handler。還能夠處理0bytes的申請.
operator delete應在收到null後什麼也不做
52.。。
54.熟悉tr1在內的標準程序庫

55.熟悉boost

後記:這個需要過第二遍再整理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章