Effective C++ Notes(讀書筆記)

1,視C++爲一種語言聯邦,大體分爲4個部分:ios

A)C。說到底C++還是以C爲基礎。區塊、語句、預處理器、內置數據類型、數組、指針等等通通來自C。算法

B)Object-Oriented C++。這部分也就是C with Classes所訴求的:classes(包括構造函數和虛構函數)、封裝、繼承、多態,虛函數等等。編程

C)Template C++。這是C++的範型編程部分,tamplates威力強大,它給咱們帶來了嶄新的編程範型,也就是所謂的TMP模板元編程。數組

D)STL。STL是個template程序庫,它對容器、迭代器、算法以及函數對象的規範有極佳的緊密配合與協調,而後template及程序庫也能夠其餘想法建置出來。安全

2,儘可能使用const,enum,inline代替#define函數

A)#define不被視爲語言的一部分,屬於預處理指令,編譯不會計入符號表沒法調試。學習

B)#define在預處理器處理階段只作簡單的替換,這將帶來不少預期意外的行爲。如this

#define MAX(a, b) ((a)>(b)?(a):(b))

儘管上述宏定義已將變量用括號括起來了,可是仍是不能避免MAX(++a, b+10)這樣給a所帶來的兩次不是預期內的自增行爲。覺得替換爲:spa

template<typename T>
inline T Max(const T& a, const T& b)
{
    return a > b ? a : b;
}

3,儘量使用const設計

A)修飾指針

char* p = 「hello」 //non-const pointer, non-const data
const char* p = 「hello」 //non-const pointer, const data
char* const p = 「hello」 //const pointer,non-const data
const char* const p = 「hello’ //const pointer, const data

B)修飾迭代器

const std::vector<int>::iterator it = vec.begin(); //const pointer,non-const data
*it = 10 ;                 //no problem
it ++;                     //error
std ::vector<int>::const_iterator it = vec.begin() //non-pointer, const data
*it = 10;                 // error
it ++;                     //no problem

C)修飾函數參數、返回值、成員函數

class TextBlock {
public:
const char& operator[](std::size_t pos) const
{
    return text[pos];
}
char & operator[](std::size_t pos)
{
    return const_cast<char&>(static_cast<const TextBlock&>(*this)[pos]);
}
private:
    char text[32];
};

4,肯定對象被使用前已被初始化

A)區分變量初始化和變量賦值二者之間的區別

B)警戒在C++中類未初始化完成以前就使用的問題,由於沒法肯定類與類之間的初始化順序

5,瞭解C++默默編寫並調用那些函數

編譯器能夠暗自爲class建立default構造函數,copy構造函數,copy assignment賦值操做符,以及析構函數.

6,若不想使用編譯器自動生成的函數,就該明確拒絕

將copy構造函數和賦值操做符聲明爲private成員函數且不去實現它們.

7,爲多態基類聲明virtual析構函數

delete一個具備多態性質的基類指針是未定義的行爲,這將致使派生類的析構函數沒法正常調用.由於爲具備多態性質的基類定義virtual析構函數.

8,別讓異常逃離析構函數

在析構函數中發生的異常不容許擴散出去,應該捕獲異常,並選擇終止或吞下該異常.

9,毫不在構造或者析構函數中調用virtual函數

這絕對視一種詭異的行爲...

10,令operator=返回一個reference to *this

這是一個實現的協議,爲了兼容連鎖賦值操做,就像這樣:

Wiget& operator=(const Wiget& rhs)
{
    //do something
    return *this;
}

11,在operator=中處理」自我賦值」

潛在的自我賦值必然存在,並且未必能立馬識別出來.既然實現了operator=就必然要考慮到自我賦值的狀況,參見10點

Wiget& operator=(const Wiget& rhs)
{
    if (this == &rhs)
        return *this;
    //do something
    return *this;
}

12,複製對象時視勿忘其每個成分

新增成員後忘記對拷貝構造函數和複製操做函數進行同步修改;

若是基類實現了複製操做函數,在派生類的複製操做函數應顯示調用,以保證基類也被正確的複製.

13,以對象管理資源

A)爲了防止內存泄露,請使用RAII對象,在構造函數中獲取資源,在析構函數中釋放資源,用棧中的局部變量管理堆內存。

B)std::auto_ptr只保存一份指針對象,賦值語句的右值將被置爲NULL。而std::tr1::shared_ptr則是引用計數型智能指針,可是隻能針對單個對象使用,對象數組應使用shared_array。

std::auto_ptr<Wiget> ap(new Wiget);
std::tr1::shared_ptr<Wiget> sp(new Wiget);

14,在資源管理類中當心copying行爲

用來管理堆內存的RAII對象發生了複製行爲會怎樣?若是RAII對象是淺拷貝,這將簡單的複製指針,當RAII對象析構的時候,指針指向的內存將被重複釋放,若是RAII對象視深拷貝,這複製一份指針指向的內存,雖然正常但不是咱們所要的。避免發生複製行爲:

A)顯試定義拷貝構造函數和賦值操做符函數,但不實現它,禁止複製行爲。

B)使用複製增長引用計數的方式來肯定什麼時候內存應該被釋放

不只能夠管理內存,還能夠管理其它資源,以下:

class Lock {
public:
explicit Lock(Mutex* pm):mutextPtr(pm, unlock)
{
    lock(mutexPtr.get());
}
private:
    std::tr1::shared_ptr<Mutext> mutexPtr;
};

調用:

Mutex m;
{
    Lock ml(&m); //進入臨界區
    ..... //不再用擔憂ml被複制了
}

局部變量被自動釋放。shared_ptr自動調用刪除器unlock,解鎖臨界區。

15,在資源管理類中提供對原始資源的訪問

提供get方法,或者重載operator->,operator*,不提倡operator T() const,可能帶來隱式轉換。

16,成對使用new和delete要採用相同的形式

new對應delete,new[]對應delete[]。警戒typedef矇蔽了你的雙眼。如:

typedef int vec[100];
int *p = new vec;
delete p;             //error

17,以獨立的語句將newd對象置於shared_ptr之中

process(std::tr1::shared_ptr<Wiget> pW(new Wiget), f1());

上述函數參數作了3件事:new Wiget, f1(), 構造shared_ptr;順序不能肯定,假如f1()異常將致使new Wiget丟失。應該將智能指針構造獨立出來:

std::tr1:;shared_ptr<Wiget> pW(new Wiget);
process(pW, f1());

18,讓接口容易被正確使用,不易被誤用

A)重載operator*時返回const 對象,禁止被當作左值使用。

B)確保接口能被正確的調用

C)誰使用誰負責的思想在跨DLL時行不通,new/delete成對使用將致使運行時錯誤,應使用shared_ptr提供的刪除器,將內存管理職責收回。

19,設計Class猶如設計Type

A)新type的對象應該如何被建立和銷燬?new/delete,new[]/delete[]

B)對象初始化和對象賦值又怎樣的差異?不要混淆什麼是初始化,什麼是賦值

C)新type的對象被pass by value意味着什麼?對象拷貝

D)什麼是新type的合法值?約束成員的屬性

E)新的type須要配合某個繼承圖系嗎?注意多態應該實現virtual析構函數

F)新type須要什麼樣的轉換?explicit 構造函數不允許隱式轉換,可是數值類型例外

G)什麼樣的操做符和函數對新type而言是合理的?約束行爲屬性

H)什麼樣的標準函數應該駁回?約束class的默認行爲

I)誰該取用新type的成員?類的封裝和抽象

J)什麼視新type的「未聲明接口」?資源管理,效率,安全性定義

K)新的type有多麼通常化?模板化,特化

L)你真的須要一個新的type?一個新的type以上都是要考慮的

20,寧以pass-by-reference-to-const替換pass-by-value

A)前者更加高效,避免了賦值類的開銷,也避免了類被切割問題;

B)除了自定義類(也有例外),內置類型和STL迭代器、函數對象傳值更加穩當。

21,返回對象時,別妄想返回其reference

局部變量和臨時變量不能做爲指針或者引用返回,其內存隨做用域結束而釋放。

22,將成員變量聲明爲private

所謂越是看不見牽扯越少,提供更好的封裝性,數據一致性,彈性。

23,寧以non-member,non-friend替換member函數

24,若全部參數皆需類型轉換,請爲此採用non-member函數

佐證第23條,operator*實現兩個不一樣版本哪一個好?

版本1:

class Rational {
public:
    const Rational operator*(const rational& rhs) const;
};

版本2:

class Rational {};
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
    return Rational(lhs..., rhs...); 
}

參考代碼:

#include <stdio.h>
 
class Rational 
{
public:
    Rational();
    Rational(int ca = 0, int cb= 0)
        : a(ca), b(cb)
     {}
     void print()
     {
        printf("%d, %d\n", a, b);
     }
public:
     int a;
     int b;
};
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
  return Rational(lhs.a + rhs.a, lhs.b + rhs.b);
}
 
int main(int argc, char* argv[])
{
  Rational r1(1, 2);
  Rational r2(3, 4);
  Rational r3 = r1 * r2;
  Rational r4 = r1 * 2;
  Rational r5 = 2 * r1;
  r1.print();
  r2.print();
  r3.print();
  r4.print();
  r5.print();
  return 0;
}

25,考慮寫一個不拋出異常的swap函數

正確的調用std::swap:

using std::swap;
swap(obj1, obj2);

26,儘量的延後變量定義式的出現時間

爲了改善程序效率,遵循使用時再定義的原則。

27,盡少作轉型動做

const_cast將對象的常量性移除

static_cast強迫隱式轉換

dynamic_cast執行安全向下轉型,不建議使用一般使用virtual函數實現調用

reinterpret_cast執行低級轉型

28,避免返回handles指向對象內部成分

將成員定義爲private,由提供方法get出來返回成員的引用、指針或迭代器,這是自相矛盾的。

29,爲「異常安全」而努力是值得的

異常安全函數會不泄露任何資源,不允許數據被破壞。

void PrettyMenu::changeBackgroud(std::istream& imgSrc)
{
    lock(&mutex);
    delete bgImage;
    bgImage = new Image(imgSrc); //異常發生點
    unlock(&mutex); //永遠不會被unlock
}
void PrettyMenu::changeBackgroud(std::istream& imgSrc)
{
    Lock ml(&mutex) //參看第14點定義
    delete bgImage;
    bgImage = new Image(imgSrc); //異常發生點
    //函數返回就會unlock
}

30,透過了解inlining的裏裏外外

inline減小了函數調用的開銷,何時申明爲inline函數應該謹慎。

31,將文件間的編譯依存關係降至最低

對於C++類而言,若是它的頭文件變了,那麼全部這個類的對象所在的文件都要重編,但若是它的實現文件(cpp文件)變了,而頭文件沒有變(對外的接口不變),那麼全部這個類的對象所在的文件都不會因之而重編。所以,避免大量依賴性編譯的解決方案就是:在頭文件中用class聲明外來類,用指針或引用代替變量的聲明;在cpp文件中包含外來類的頭文件。

32,肯定你的public繼承塑模出來視is-a關係

33,避免掩蓋繼承而來的名稱

派生類會掩蓋全部基類的同名函數,可以使用using base::func;在派生類中可見。

34,區分接口繼承和實現繼承

pure virtual函數只具體指定接口繼承

impure virtual函數具體指定接口繼承及缺省實現繼承

non-virtual函數具體指定接口繼承以及強制性實現繼承

35,考慮virtual函數之外的其它選擇

A)使用non-virtual interface(NVI)手法

B)將virtual函數替換爲「函數指針成員變量」

C)以tr1::function成員變量替換virtual函數

D)將一個繼承體系內的virtual函數替換爲另外一個繼承體系內的virtual函數

#include <stdio.h>
#include <tr1/memory>
#include <tr1/functional>
 
class GameCharacter 
{
public:
  void healthValue()
  {
    printf("before %s\n", __FUNCTION__);
    doHealthValue();
    printf("after %s\n", __FUNCTION__);
  }
private:
  virtual void doHealthValue()
  {
    printf("GameCharacter doHealthValue\n");
  }
};
 
class Player : public GameCharacter 
{
private:
  virtual void doHealthValue()
  {
    printf("Player doHealthValue\n");
  }
};
 
class GameCharacter2;
 
void defaultHealthCalc(const GameCharacter2& gc)
{
  printf("%s\n", __FUNCTION__);
}
 
void dogHealthCalc(const GameCharacter2& gc)
{
  printf("%s\n", __FUNCTION__);
}
 
class GameCharacter2 
{
public:
  typedef void (*HealthCalcFunc)(const GameCharacter2&);
  explicit GameCharacter2(HealthCalcFunc hcf = defaultHealthCalc)
    : m_healthFunc(hcf)
  {}
  void healthValue() const
  {
    m_healthFunc(*this);
  }
private:
  HealthCalcFunc m_healthFunc;
};
 
class GameCharacter3;
 
void defaultObjFunc(const GameCharacter3& gc)
{
  printf("%s\n", __FUNCTION__);
}
 
void dogObjFunc(const GameCharacter3& gc)
{
  printf("%s\n", __FUNCTION__);
}
 
class GameCharacter3 
{
public:
  //typedef void (*HealthCalcFunc)(const GameCharacter3&);
  typedef std::tr1::function<void (const GameCharacter3&)> ObjFunc;
  explicit GameCharacter3(ObjFunc hcf = defaultObjFunc)
    : m_healthFunc(hcf)
  {}
  void healthValue() const
  {
    m_healthFunc(*this);
  }
private:
  ObjFunc m_healthFunc;
};
 
class GameCharacter4;
class CHealthCalc 
{
public:
  virtual int calc(const GameCharacter4& gc) const
  {
    printf("CHealthCalc::%s\n", __FUNCTION__);
  }
};
CHealthCalc defaultCHealthCalc;
class GameCharacter4 
{
public:
  explicit GameCharacter4(CHealthCalc* pChc = &defaultCHealthCalc)
    : pHealthCalc(pChc)
  {}
  int healthValue() const
  {
    return pHealthCalc->calc(*this);
  }
private:
  CHealthCalc* pHealthCalc;
};
 
int main(int argc, char* argv[])
{
  //NVI手法
  GameCharacter* p = new Player;
  p->healthValue();
  delete p;
  //將virtual函數替換爲「函數指針成員變量」
  GameCharacter2 gc1;
  gc1.healthValue();
  GameCharacter2 gc2(dogHealthCalc);
  gc2.healthValue();
  //以tr1::function成員變量替換virtual函數
  GameCharacter3 go1;
  go1.healthValue();
  GameCharacter3 go2(dogObjFunc);
  go2.healthValue();
  //將一個繼承體系內的virtual函數替換爲另外一個繼承體系內的virtual函數
  GameCharacter4 gcl1;
  gcl1.healthValue();
 
  return 0;
}

36,毫不從新定義繼承而來的non-virtual函數

37,絕對不要從新定義繼承而來的參數值

39,明智而審慎的使用private繼承

40,明智而審慎的時候多重繼承

41,瞭解隱式接口和編譯器多態

面向對象編程世界老是以顯式接口和運行期多態來解決問題,而模板元編程這相反。

發生在編譯期間的template具現化成爲編譯期多態。

class和template都支持接口和多態。

對class而言接口是顯示的,以函數簽名爲中心,多態則是經過virtual函數發生在運行期。

對template參數而言,接口是隱式的,奠定於有效表達式,多態則是經過template具現化和函數重載解析發生於編譯期。

42,瞭解typename的雙重意義

聲明template參數時,前綴關鍵字class和typename是徹底同樣的。

請使用typename標識嵌套從屬類型名稱,但不得在基類列或成員初始列內使用。

43,學習處理模板化基類內的名稱

模板特化即針對某種類型的進行特殊處理,再也不經過通用模板編譯代碼。

派生類模板調用基類模板的成員函數時應告訴編譯期怎樣調用,可經過this或using 指明調用基類函數

44,將與參數無關的代碼抽離

這些代碼將致使template具現化所帶來的代碼膨脹

45,運用成員函數模板接受全部兼容類型

如何在模板內定義成員函數模板,在定義了泛化構造和賦值操做函數以後,仍應顯示定義通常式。

46,須要類型轉換時請爲模板定義非成員函數

請參看第24點

#include <iostream>
 
template<typename T>
class Rational;
 
template<typename T>
const Rational<T> doMultiply(const Rational<T>& , const Rational<T>&);
 
template<typename T>
class Rational 
{
public:
  Rational<T>(T ca, T cb)
    : a(ca), b(cb)
  {}
  void print()
  {
    std::cout << a << "," << b << std::endl;
  }
  friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
  {
    return doMultiply(lhs, rhs);
  }
public:
  T a;
  T b;
};
 
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs)
{
  return Rational<T>(lhs.a + lhs.a, lhs.b + rhs.b);
}
 
int main(int argc, char* argv[])
{
  Rational<int> r1(1, 2);
  Rational<int> r2(3, 4);
  Rational<int> r3 = r1 * r2;
  Rational<double> r4(10.0, 20.0);
  Rational<double> r5 = r4 * r4;
  r1.print();
  r2.print();
  r3.print();
  r4.print();
  r5.print();
  return 0;
}

47,請使用traits classes變現類型信息

48,認識template模板元編程

#include <stdio.h>
 
template<unsigned n>
struct ftor 
{
  enum{nValue = n * ftor<n-1>::nValue};
};
 
template<>
struct ftor<0>
{
  enum{nValue = 1};
};
 
int main(int argc, char* argv[])
{
  printf("%d\n", ftor<5>::nValue);
  return 0;
}

49,瞭解new-handler的行爲

std::set_new_handler
std::get_new_handler

#include <iostream>
#include <new>
#include <cstdio>
#include <cstdlib>
 
namespace std
{
  typedef void (*new_handler)();
  new_handler set_new_handler(new_handler p) throw();
};
 
void OutofMemory()
{
  char szBuff[128] = "";
  snprintf(szBuff, sizeof(szBuff), "%s:out of memory!!!", __FUNCTION__);
  std::cout << szBuff << std::endl;
  std::abort();
}
 
//RAII class
class NewHolder 
{
public:
  explicit NewHolder(std::new_handler nh)
    : handler(nh)
  {}
  ~NewHolder()
  {
    std::set_new_handler(handler);
  }
private:
  std::new_handler handler;
  NewHolder(const NewHolder&);
  NewHolder& operator=(const NewHolder&);
};
 
//New Handler Support class
template<typename T>
class NewSupport
{
public:
  static std::new_handler set_new_handler(std::new_handler p) throw();
  static void* operator new(std::size_t size) throw(std::bad_alloc);
  static void* operator new[](std::size_t size) throw(std::bad_alloc);
private:
  static std::new_handler currentHandler;
};
 
template<typename T>
std::new_handler NewSupport<T>::set_new_handler(std::new_handler p) throw()
{
  std::new_handler oldHandler = currentHandler;
  currentHandler = p;
  return oldHandler;
}
 
template<typename T>
void* NewSupport<T>::operator new(std::size_t size) throw(std::bad_alloc)
{
  NewHolder h(std::set_new_handler(currentHandler));
  return ::operator new(size);
}
 
template<typename T>
void* NewSupport<T>::operator new[](std::size_t size) throw(std::bad_alloc)
{
  NewHolder h(std::set_new_handler(currentHandler));
  return ::operator new [](size);
}
 
template<typename T>
std::new_handler NewSupport<T>::currentHandler = NULL;
 
class Test : public NewSupport<Test>
{
public:
  Test()
  {
    //p = new int[10000000000L];
  }
  ~Test()
  {
    //delete p;
    //p = NULL;
  }
  void print()
  {
    std::cout << "Test Class print()!!!" << std::endl;
  }
private:
  int* p;
};
 
int main(int argc, char* argv[])
{
  // 設置全局的handler
  //  std::set_new_handler(OutofMemory);
  //  int* p = new int[10000000000L];
  //  delete p;
  
  // 設置class Test的handler
  //  Test::set_new_handler(OutofMemory);
  //  Test* p = new Test[10000000000L];
  
  // 看看在哪裏handler了
  //  Test::set_new_handler(OutofMemory);
  //  Test* p = new Test;
  //  p->print();
 
  //  delete p;
  return 0;
}

50,定製new和delete

A)爲了效能

B)爲了收集使用上的統計數據

C)爲了檢測運用錯誤

D)爲了收集動態分配內存之使用統計信息

E)爲了增長分配和歸還速度

F)爲了下降缺省內存管理器帶來的空間額外開銷

G)爲了彌補缺省分配器中的非最佳位對齊

H)爲了將對象成簇集中

51,編寫new和delete時需固守常規

52,寫了placement new也要寫placement delete

定製版的new/delete要對應,同時主要不要掩蓋正常版本

53,不要忽略編譯器警告

嚴肅對待編譯期發出來的抱怨,也不能過度依賴編譯,每一個編譯器都有不一樣。

54,讓本身熟悉包括TR1在內的標準程序庫

55,讓本身熟悉boost

相關文章
相關標籤/搜索