PS,1880后程序員

          看不完的牙,寫不完的程序,跑不完的步。
          隨筆 - 97, 文章 - 34, 評論 - 10, 引用 - 0
          數據加載中……

          Effecitive C++讀書筆記 第五章 實現 Implementations

           

          Item26Postpone variable definitions as long as possible.盡可能延后變量定義的出現時間

          這里所說的變量是variable of type,它需要調用constructordestructor

          簡單情況下,如果函數拋出異常,導致變量沒有被使用到,但是由于已經定義過,其實還是需要調用它的constructordestructor

          std::string encryptPassword(const std::string& password)

          {

          std::string encrypted; //這是錯誤的寫法,這個定義已經需要調用構造函數了。

          encrypted = password;

          encrypt(encrypted);

          return encrypted;

          }

          正確的寫法:

          std::string encryptPassword(const std::string& password)

          {

          std::string encrypted(password); //正確的寫法。

          encrypt(encrypted);

          return encrypted;

          }

          postpone有兩層含義:

          1 postpone a variable’s definition until right before you have to use the variable

          2 postpone the definition until you have initialization arguments for it.

          循環怎么辦?

          最常見的寫法一種是在循環體里面定義變量,另一種是在循環體執行前定義一個變量。

          A

          B

          approach

          Widget w;

          for(int i=0; i<n;++i)

          {

          w=取決于i的某個值;

          }

          for(int i=0; i<n;++i)

          {

          Widget w(取決于i的某個值);

          }

          開銷

          1 constructor + 1 destructor + n assignment

          if assignment小于 constructor-destructor對,這種寫法比較有效

          n constructor + n destructor

          defect

          這種方法定義的變量在較大的范圍都是可見的,不好。

          結論:除非你明確知道approach A的開銷小于approach B,否則盡量使用approach B

          Item 27Minimize casting盡量少做轉型動作

          C++提供的4中cast形式

          const_cast<T>(expression)

          dynamic_cast<T>(expression)

          reinterpret_cast<T>(expression)

          static_cast<T>(expression)

          盡量使用這些新風格的轉型,最起碼的它好認啊。

          cast,到底做了什么?

          基類和派生類的指針有時是不相等的,存在一個偏移量offsetsingle object might have more than one address。為啥說有時呢,因為這取決于Compiler。所以不能想當然在基類指針上加個值就得到什么。

          dynamic_cast

          cast并不是啥都沒有做的,它會創建一個臨時的基類的copy,然后調用的是這個臨時類的方法,和當前被cast類無關哦。

          同時多級的繼承關系里面cast也要通過字符串比較來確定匹配的基類,因此如果對性能有要求,就盡量少使用cast

          解決方法

          有兩種:

          1 使用type-safe的容器

          這種方法的缺陷是容器里面只能允許有1種類型的指針

          ExampleP121

          2 在基類里面定義虛函數

          class Window{

          public:

          virtual void blink();

          };

          class SpecialWindow : public Window {

          public:

          virtual void blink();

          };

          typedef std::vector<std::tr1::shared_ptr<Window>> VPW;

          VPW winPtrs;

          for(VPW::iterator iter=winPtrs.begin(); iter!=winPtrs.end(); ++iter)

          (*iter).blink(); //注意這里就不需要dynamic_cast

          Item 28Avoid returning "handles" to object internals.避免返回handles指向對象內部成分 123

          handles

          handles可以理解為句柄,或者說是一個“把柄”,握著這個“把柄”,你可以修改對象的數據成員。

          handle包括referencepointeriterator

          問題出現的背景

          很簡單,就是看上去函數返回的是一個const對象,但是由于是handle,導致數據還是可以被client修改的。這種情況,debug時很難發現,而且給人的感覺是系統不穩定,后果很嚴重,不可輕視。

          struct rectData {

          Point ulhc;

          Point lrhc;

          };

          class Rectangle {

          public:

          Point& upperLeft() const {return pData->ulhc; }

          private:

          std::tr1::shared_ptr<RecData> pData;

          };

          const member function:不能修改數據成員。

          client調用:

          const Rectangle rec(Point (0,0), Point(100,100));

          rec.upperLeft().setX(50);//坐標就這么不知不覺中被修改了

          結論

          避免返回指向對象內部的handles,這種handles包括:pointerreferenceiterator

          如果某個函數的訪問級別低,禁止其它成員函數返回指向這個函數的指針。因為如果這樣,就可以通過函數指針來調用這個函數了。

          Item 29Strive for exception-safe code.為“異常安全”而努力是值得的 127

          class PrettyMenu{

          public:

          void PrettyMenu::changeBackgroud(std::istream& imgSrc) ;

          private:

          Mutex mutex;

          int imageChanges;

          Image * bgImage;

          };

          void PrettyMenu::changeBackgroud(std::istream& imgSrc)

          {

          Lock m1(&mutex); //Item13,對象管理資源的典型應用

          delete bgImage;

          ++imageChanges;

          bgImage = new Image(imgSrc);

          }

          異常安全性函數應該提供以下3種保證之一:

          基本保證

          如果異常拋出,程序里所有的東西都保持有效的狀態。

          增強保證

          如果異常拋出,程序的狀態保持不變。

          不拋出異常保證

          決不拋出異常。因為總能夠完成希望要做的事情。

          這同時也是一種理想狀態。

          一般情況下,我們的選擇是在basicstrong異常保證之間。

          重新寫changeBackgroud,提供basic異常保證:

          class PrettyMenu{

          std::tr1::shared_ptr<Image> bgImage;

          };

          void PrettyMenu::changeBackgroud(std::istream& imgSrc)

          {

          Lock m1(&mutex); //Item13,對象管理資源的典型應用

          bgImage.reset(new Image(imgSrc) );

          ++imageChanges;

          }

          strong異常保證的設計策略說出來也很簡單,就是copy and swap。具體描述見P131 L5開始的描述。

          strong異常保證

          基本思路就是copy and swap

          struct PMImpl {

          std::tr1::shared_prt<Image> bgImage;

          int imageChanges;

          };

          class PrettyMenu{

          private:

          Mutex mutex;

          std::tr1::shared_ptr<PMImpl> pImpl;

          };

          void PrettyMenu::changeBackgroud(std::istream& imgSrc)

          {

          using std::swap;

          Lock m1(&mutex); //Item13,對象管理資源的典型應用

          std::tr1:: shared_prt< PMImpl > pNew(new PMImpl(*pImpl));

          pNew->bgImage.reset(new Image(imgSrc)); //修改副本

          ++pNew-> imageChanges;

          swap(pImpl, pNew); //置換

          }

          trade-off折中考慮異常保證

          并不是一定要提供strong異常保證就是好的,這里是需要一個trade-offstrong異常保證的代價一定是很大的,efficiencycomplexity都是要考慮的因素。

          同時如果函數中調用了沒有異常保證的其它函數,這個函數也不能是異常保證的。

          void someFunc()

          {

          f1();

          f2();

          }

          這引申出一個結論:如果代碼中調用了沒有異常保證的C的傳統代碼,這樣的函數,就大可不必還要提供strong的異常保證了

          Item 30Understand the ins and outs of inlining.透徹了解inlining的里里外外 134

          什么是內聯函數inline

          將函數指定為 inline 函數,(通常)就是將它在程序中每個調用點上“內聯地”展開。函數本體Function body是不存在的。

          inline是對Compiler的請求,而不是對命令的。

          The inline specification is only a request to the compiler. The compiler may choose to ignore this request.

          一般來說,內聯機制適用于優化小的、只有幾行的而且經常被調用的函數。

          大多數的編譯器都不支持遞歸函數的內聯。

          復雜函數也是不可以的,一個 1200 行的函數也不太可能在調用點內聯展開。

          虛函數也是不可以的。

          inline與函數模板Function Template

          template<typename T>

          inline const T& std::max(const T& a, const T& b)

          { return a<b?b:a; }

          inline函數大多數都是定義在頭文件了,這是因為編譯器必須知道inline函數是什么樣子。

          Template也是定義在頭文件里,因為Compiler需要知道Template是什么樣子,為了初始化Template

          Template獨立于inline。這是不同的概念,當然函數模板也可以聲明成inline

          函數指針和內聯函數

          函數指針指向的內聯函數一定不會被inlineCompiler會生成這個函數的本體function body,函數指針就是指向的這個函數本體。

          inline void f() {…}

          void (*pf)() = f;

          f();

          pf();//這個調用就不會是inline,因為這是一個函數指針。

          構造函數、析構函數和內聯函數

          構造函數和析構函數不要做內聯。

          簡單的說,如果base constructor內聯,派生類中從基類繼承來的data member被初始化2次,一次是來自內聯,一次來自C++執行基類的構造函數。

          同理,析構函數,也就會被釋放2L慘了,但是實際上,只有一個對象啊。不死等什么。

          內聯函數修改的影響

          內聯函數的機制決定調用內聯函數,就是把代碼插入調用的位置,因此如果內聯函數修改了,所有調用該函數的地方都需要重新編譯。如果這個函數不是內聯的,修改后,只要重新鏈接就可以了。

          記住

          inline僅僅適用于小型的,被頻繁調用的函數。

          不要因為函數模板定義在頭文件里就把它聲明成inclineFunction Templateinline這是2個不同的概念。

          Item 31Minimize compilation dependencies between files.將文件間的編譯依存關系降至最低 140

          前向聲明forward declaration

          1.            首先前向聲明僅僅是聲明了一個類

          class Date; //forward declaration

          class Address;

          2.            其次前向聲明是一種不完全類型

          只能以有限的方式使用。只能用于定義指向該類型的指針,或者用于聲明(而不是定義)使用該類型作為形參類型或返回類型的函數。不能定義該類型的對象。

          pimplpointer to implementation

          #include <string>

          #include <memory>

          class PersonImpl;

          class date;

          class Address;

          class Person {

          public:

          Person(const std::string& name, const date& birthday, const Address& addr);

          std::string name() const;

          std::string birthday() const;

          std::string address() const;

          private:

          std::tr1::shared_ptr<PersonImpl> pImpl; //point to implementation

          };

          基本解決之道

          replacement of dependencies on definitions with dependencies on declarations.以聲明的依賴性替換定義的依賴性

          盡量讓頭文件做到自包含,萬一做不到,則讓它和其它文件內的聲明相互依賴,而不是定義。

          規則

          如果使用對象引用活對象指針可以解決問題,就避免使用對象,

          取代類定義,依賴于類聲明

          頭文件要成對出現,一個是聲明,一個是定義。

          兩種具體的操作方法

          Handle Classes

          定義Handle class

          class Person {

          public:

          Person(const std::string& name, const Date& birthday, const Address& addr);

          std::string name() const=0;

          std::string birthday() const=0;

          std::string address() const=0;

          private:

          std::tr1:shared_ptr<PersonImpl> pImpl;

          };

          std::string Person::name() const

          {

          return pImpl->name();

          }

          定義實現類PersonImpl

          Handle Classes

          這里給出的Handle Classes的定義太精辟了。喜歡J

          Classes like Person that employ the pimpl idiom are often called Handle classes.

          Making Person a Handle class doesn’t change what Person does, it just changes the way it does it.

          Interface Classes

          定義接口

          class Person {

          public:

          virtual ~Person();

          virtual std::string name() const=0;

          virtual std::string birthday() const=0;

          virtual std::string address() const=0;

          static std::tr1::shared_ptr<Person> Person::create(const std::string& name, const Date& birthday, const Address& addr );

          };

          定義實現

          class RealPerson:public Person {

          public:

          realPerson(const std::string& name, std::string& birthday, const Address& addr)

          :theName(name),theBirthdate(birthday), theAddress(addr)

          {}

          virtual ~RealPerson()

          std::string name() const;

          std::string birthday () const;

          std::string address () const;

          private:

          std::string theName;

          date theBirdate;

          Address theAddress;

          };

           

          std::tr1::shared_ptr<Person> Person::create(const std::string& name, const Date& birthday, const Address& addr )

          {

          return std::tr1::shared_ptr<Person>(new RealPerson(name, birthday,addr));

          }

          結論

          減少耦合行的慣用的思路是用聲明依賴替代定義依賴。有2種方法:Interface classesHandle classes.

          任何減少耦合的做法都是會帶來負面影響的:占用內存增加,間接調用。但是關鍵還是在于引用本身是不是對這些額外的開銷敏感。

          posted on 2010-03-22 11:19 amenglai 閱讀(414) 評論(0)  編輯  收藏 所屬分類: Effecitive C++讀書筆記

          主站蜘蛛池模板: 鄄城县| 马山县| 太和县| 偏关县| 伊川县| 凤台县| 贵州省| 盘锦市| 宜良县| 新乐市| 新昌县| 什邡市| 阿城市| 满洲里市| 万载县| 明溪县| 浦东新区| 霸州市| 莒南县| 井冈山市| 灯塔市| 溧水县| 彭水| 奎屯市| 咸阳市| 新宁县| 永吉县| 洛南县| 卢氏县| 呼伦贝尔市| 云梦县| 砀山县| 淳化县| 武安市| 固始县| 甘泉县| 财经| 莒南县| 项城市| 红原县| 稻城县|