PS,1880后程序員

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

          Effecitive C++讀書筆記 Chapter4 設計與聲明

           

          Item18 Make interfaces easy to use correctly and hard to use incorrectly.讓接口容易被正確使用,不易被誤用

          好的接口設計應該是既容易被正確使用,又不易被誤用。

          就例如書中的Sample,關于時間的,我們一般的做法就是在創建Day對象時,追加校驗函數來判斷年月日是不是有效。

          建議的做法一是:創建新的類型

          定義:

          class Month {

          public:

             static Month Jan() {return Month(1); }

             static Month feb() {return Month(2); }

          private Month(int m);

          };

          Date d(Month::Mar(), Day(30), Year(1995) );

          為啥不直接使用靜態變量?

          參考Item4 P30,簡單說就是不能保證在使用non-local static objects時,這個對象就已經初始化了。如果non-local static objects在另一個文件了,又恰巧沒有初始化,系統當然就會翹辮子了。

          另一種方法:加上const來限制type可以做的事情。

          先參考Item3 P19

          class Rational { };

          const Rational operator*(const Rational &lhs, const Rational &rhs);

          之所以強制設置為const就是為了避免client在使用時出錯。因為如果沒有clien,那么:

          Rational a,b,c;

          (a*b)=c

          這種寫法是對的,但是如果ab是內置類型,這種寫法就是錯誤的。

          除非有必要,否則就要保證你的類型type的行為和內置類型一致。

          一致性導致接口容易被正確使用。

          STL是榜樣,java在這里成了反面教材,因為如果想知道容器內對象的數量,用Array,要訪問屬性lengthString要用length函數,ArrayList要用size函數,這就是不一致性。

          使用std::tr1::shared_ptr,消除client對資源管理的責任

          找出以下寫法的兩個易錯的地方:

          Investment* createInvestment();

          1 忘記刪除createInvestment()返回的指針

          2 刪除這個指針多次

          so,修改定義:

          std::tr1::shared_ptr< Investment > createInvestment();

          如果出現這種情形:從createInvestment得到Investment*的函數要把這個指針傳遞個給叫做getRidOfInvestment,由getridOfInvestment取代使用delete

          這里就出現了一個新的client易錯的點,用戶或許會使用錯的資源釋放機制。因為deletegetRidOfInvestment取代了。

          std::tr1::shared_ptr< Investment >

          pInv(static_cast<Investment*>(0), getRidOfInvestment);

          那么定義就應該是這樣的:

          std::tr1::shared_ptr< Investment > createInvestment()

          {

             std::tr1::shared_ptr< Investment >

          retVal(static_cast<Investment*>(0), getRidOfInvestment); //這不能讓client來做

          retVal = ;

          return retVal;

          }

          tr1::shared_ptr的優點是允許在一個DLL創建對象,在另一個DLL里刪除對象。

          牢記

          • Good interfaces are easy to use correctly and hard to use in correctly.
          • 接口一致性,于內置數據類型的行為兼容
          • 阻止錯誤的方式還包括創建新的類型(例如Month),限制類型上的操作,束縛對象值,以及消除客戶的資源管理的責任
          • tr1::shared_ptr支持定制類型的刪除器deleter,允許在一個DLL創建對象,在另一個DLL里刪除對象。

          Item19Treat class design as type design.設計class猶如設計type

          在設計一個類的時候,要回答一系列的問題哦。

          參考大師在P85-P86之間給出的常常的清單吧,其實實際上,我在設計類的時候的確沒有想過這么多,問過自己這么的為什么,所以這也是我總是在追求代碼重用,卻總是發現自己寫的代碼重用度很低的一個原因把。

          Item20Prefer pass-by-reference-to-const to pass-by-value.寧以pass-by-reference-to-const替換pass-by-value

          But先學習一個單詞,characteristicKAO,這竟然是個名詞。

          再學一個地道的說法:解決問題的方法:The way around the slicing problem is…

          函數都是值傳遞。pass by-valuefunction parameters are initialized with copies of the actual arguments, and function callers goes back a copy of the value returned by the function.這樣當然開銷就大了,每次都先copy一份進來,完事以后,再copy一份出去。

          假設函數的參數是一個Student對象,bool validateStudent(Student s);調用這個函數,額外的隱性開銷包括要先調用copy constructor創建一個Student對象用于函數內部,函數執行結束再調用析構函數釋放這個對象。

          開銷太大了,改進一下:pass by reference-to-const

          bool validateStudent(const Student& s);

          引用是通過指針來實現實現的,因此傳遞引用實際上就是在傳遞指針。references are typically implemented as pointers.

          但是這個規則對于內置數據類型不適用,也不是適用STL iterator和函數對象function objects

          即使再小的對象也應該不要使用值傳遞,而是要使用pass by reference-to-const

          關于slicing problem的另一種描述

          slicing problem是在多態規則里面容易產生的。

          看一個簡單的基類、派生類的定義

          class Window

          {

          public:

          int height;

          int width;

          };

          class TextWindow : public Window

          {

          public:

          int cursorLocation;

          };

          Window win;

          TextWindow *tWinPtr;

          tWinPtr = new TextWindow;

          win = *tWinprt;

          win是一個Window對象,C++規定:給win分配的內存看見的大小,由其靜態類型決定。就是說默認的拷貝函數導致信息會出現丟失。這就是slicing problem

          試想一下這要是通過值傳遞的方式傳遞參數,實參一copy就已經丟失信息了。

          牢記

          • Prefer pass-by-reference-to-const over pass-by-value.這樣既有效,又可以避免slicing problem
          • 但是這個規則對于內置數據類型,STL iterator和函數對象function objects不適用。對于它們傳遞值就好了。

          Item 21Don't try to return a reference when you must return an object.必須返回對象時,別妄想返回其reference

          heap and stack

          堆和棧這是2個不同的概念,哎喲,我一直以為是一個詞。

          heap:堆

          • 棧是系統提供的功能,特點是快速高效,缺點是有限制,數據不靈活;
          • 堆是函數庫內部數據結構,不一定唯一。
          • 堆空間的分配總是動態的,雖然程序結束時所有的數據空間都會被釋放回系統,但是精確的申請內存/釋放內存匹配是良好程序的基本要素。
          • 使用new創建的對象是在heap上分配內存空間。

          stack:棧

          • 而堆是函數庫提供的功能,特點是靈活方便,數據適應面廣泛,但是效率有一定降低。
          • 棧是系統數據結構,對于進程/線程是唯一的;不同堆分配的內存無法互相操作。
          • 棧空間分靜態分配和動態分配兩種。靜態分配是編譯器完成的,比如自動變量(auto)的分配。動態分配由alloca函數完成。棧的動態分配無需釋放(是自動的),也就沒有釋放函數。為可移植的程序起見,棧的動態分配操作是不被鼓勵的!
          • 定義的局部變量是在stack上分配內存空間的。

          牢記

          • 簡單一句話就是必須要返回對象。

          Item22Declare data members private.將成員變量聲明為private

          why data members shouldn’t be public?

          argument

          (against) 爭論,意見

          實參

          形參是parameters

          protected data member is mot more encapsulated than public one.

          牢記

          • data member一定要封裝。
          • protected不必public有更好的封裝。

          Item 23Prefer non-member non-friend functions to member functions.寧以non-membernon-friend替換member函數

          這是一個典型的例子:

          class WebBrowser {

          public:

          void clearCache();

          void clearHistory();

          void removeCookies();

          };

          為了提供一個執行所有操作的函數,所以就在WebBrowser里面追加定義:

          void clearEverything();

          哎,我一直就是這么寫的,并自以為有很好的封裝,But

          void clearBrowser(WebBrowser wb)

          {

          wb.clearCache();

          wb.clearHistory();

          wb.removeCookies();

          }

          第一:前者并不比后者有很好的封裝

          這就要解釋一下什么叫做“封裝”?以及封裝的判別標準。

          封裝的判別標準:可以通過統計能夠訪問這個data的函數的數目來計算,函數越多,這個data封裝也就月不好,因此前一種寫法的封裝就沒有后者好。這也可以用來解釋Item22里面,為什么要求數據成員不能定義為public。另外增加clearEverything()作為member function,實際上是降低了封裝性。而后面的non-member non-friend functions的定義就沒有改變WebBrowser的封裝性。

          第二:后者還能提供更加靈活的打包package,增加擴展性。

          put all convenience functions in multiple header files, but one namespace.

          第三:增加函數的可擴展性。

          你可以定義自己的convenience functions,寫到一個header file里面,放到同一個namespace里面。這是member function做不到的。

          牢記

          • 優先使用non-member non-friend函數來替換member函數。

          Item 24Declare non-member functions when type conversions should apply to all parameters.若所有參數皆需類型轉換,請為此采用non-member函數

          原因:

          Parameters are eligible for implicit type conversion only if they are listed in the parameter list.

          結論:

          make operator* a non-member function, thus allowing compilers to perform implicit type conversions on all arguments.

          class Rational {

          };

          const Rational operatior*(const Rational& lhs, Rational& rhs)

          {

             return Rationan(lhs.numerator()*rhs.numerator(),

          lhs.denominator()*rhs. denominator () );

          }

          一個誤區:

          如果一個函數,和某個類相關,而又不能定義成member,那么這個函數就一定要定義成friend

          上面這個例子就說明這個說法并不正確。真愛生命,慎用friend functions

          Item 25Consider support for a non-throwing swap.考慮寫出一個不拋異常的swap函數

          1. default swap

          就是指std里面定義的swap

          1. member swap
          2. nonmember swap
          3. specializations of std::swap

          member swap

          Widget:我們希望的是交換指針,但swap實際做的是不僅copy3Widget對象,而且還copy3WidgetImpl對象。太浪費了!都低碳時代了。

          class Widget{

          void swap(Widget& other)

          {

          using std::swap;

          swap(pImpl, other.pImpl;);

          }

          };

          template<> void swap<Widget>( Widget& a, Widget&b)

          {

             a.wap(b);

          }

          nonmember swap

          接下來要討論的是如果WidgetWidgetImpl不是類而是類模板會怎么樣?

          約束條件:不能在std里面增加新的template,只能特化(specializestd內的template

          如果非要定義,say sorrybehavior is undefinedKAO,其實這比異常還討厭。

          解決方法是把它定義到一個自己的namespace里面,而不要定義到std里面。

          namespace WidgetStuff {

          template<typename T>

          class Widget{…};

          template<typename T>

          void swap(Widget<T>& a, Widget<T>& b)

          {

          a.swap(b);

          }

          }

          specializations of std::swap

          如果僅僅是針對一個class,那就特化std::swap

          If you want to have your class-specializing version of swap called in as many contexts as possible, you need to write both a non-member version in the same namespace as your class and a specialization of std::swap.

          這部分十分繞,P111還對于C++name lookup的規則進行了詳細的描述。值得重新溫習。

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

          主站蜘蛛池模板: 长宁区| 五大连池市| 从化市| 四会市| 怀宁县| 朔州市| 邹平县| 青田县| 东方市| 香格里拉县| 清河县| 诸城市| 金寨县| 洞头县| 清苑县| 鸡泽县| 马鞍山市| 保靖县| 云霄县| 安泽县| 息烽县| 温州市| 克拉玛依市| 和静县| 宣恩县| 苏尼特左旗| 班玛县| 柳江县| 高雄市| 武宣县| 芮城县| 桂阳县| 青龙| 扎鲁特旗| 大姚县| 西城区| 连南| 文安县| 惠安县| 漳浦县| 绥芬河市|