第18章 用於大型程序的工具

1.  獨立開發的庫幾乎不可避免的使用彼此相同的名字,一個庫中定義的名字可能會與其他庫中的相同名字衝突.爲了避免衝突,可以將名字定義在namespace.

2.  通過異常.我們能夠將問題的檢測和問題的解決分離,這樣程序的問題檢測部分可以不必瞭解如何處理問題.
  
有效使用異常處理需要理解
:
      
■ 在拋出異常時會發生什麼

      
■ 在捕獲異常時又會發生什麼
      
■ 用來傳遞錯誤的對象的含義
  
異常是通過拋出對象而引發的,該對象的類型決定應該激活哪個處理代碼.被選中的處理代碼是調用鏈中與該對象類型匹配卻離拋出異常位置最近的那個.
  
執行throw的時候,不會執行跟在throw後面的語句,而是將控制從throw轉移到匹配的catch,catch可是是同一函數中局部的catch,也可以在直接或間接調用發生異常的函數的另一個函數中.控制從一個地方轉移到另一個地方的重要含義有
:
     1). 
沿着調用鏈的函數提早退出
.
     2). 
一般而言,在處理異常的時候會釋放局部存儲,所以被拋出的對象就不能在局部存儲,而是用throw表達式初始化一個稱爲異常對象的特殊對象
.
  
異常對象通過複製被拋出表達式的結果創建,該結果必須是可以複製的類型
.
  
當拋出一個表達式的時候,被拋出對象的靜態編譯時類型將決定異常對象的類型
.
  
拋出指針通常是一個壞主意:拋出指針要求在對應處理代碼存在的任意地方存在指針指向的對象.如果拋出的是指針的解引用.則對象不發生動態綁定.而是截斷
.
  
棧展開期間,釋放局部對象所用的內存並運行類類型局部對象的析構函數
.
  
在某些異常進行棧展開的時候,析構函數如果又拋出自己的未經處理的另一個異常,將會導致調用標準庫terminate函數,一般而言,terminate函數將調用abort函數,強制從整個程序非正常退出.(所以我們一般也不考慮析構函數拋出異常).

3.  查找匹配的處理代碼時,異常類型與catch說明符的類型必須完全匹配:
   
■ 允許從非constconst的轉換,也就是說,const對象的throw可以與指定接受const引用的catch匹配
.
   
■ 允許從派生類型到基類類型的轉換

   
■ 將數組轉化爲指向數組類型的指針,將函數轉換爲指向函數類型的適當的指針.
在查找catch,不允許標準算術轉化,也不允許爲類類型定義的轉換
.
  
通常,如果catch字句處理因繼承而相關的類型的異常,它就應該將自己的形參定義爲引用.帶有因繼承而相關的類型的多個catch字句,必須從最低派生類型到最高派生類型排序.

4.  catch可以通過重新拋出將異常傳遞給函數調用鏈中更上層的函數.重新拋出是後面不跟類型或者表達式的一個throw: .throw語句將重新拋出異常對象,它只能出現在catch或者從catch調用的函數中,如果在處理代碼不活動是碰到空throw,就調用terminate函數.
  
雖然重新拋出不指定自己的異常,但仍然見一個異常對象沿鏈向上傳遞,被拋出的異常是原來的異常對象,而不是catch形參
.
  
捕獲所有異常catch字句
:
    

  
爲了處理來自構造函數初始化式的異常,必須將構造函數編寫爲函數測試塊.可以使用函數測試塊將一組catch字句與函數連成一個整體.

注意,關鍵字try出現在成員函數初始化之前並且測試的複合語句包圍了構造函數的函數體.catch字句既可以處理從成員初始化列表中拋出的異常,也可以處理從構造函數函數體中拋出的異常.(構造函數要處理來自構造函數初始化的異常,唯一的方法時間構造函數編寫爲函數測試塊.

5.  標準庫異常類的繼承:

exception
類型所定義的唯一操作是一個名爲What的虛成員,該函數返回const char*對象,它一般返回用來在拋出位置構造異常對象的信息.

6.  通過定義一個類來封裝資源的分配和釋放,可以保證正確的釋放資源,這一技術常稱爲資源分配即初始化簡稱RAII
  
可能存在異常的程序以及分配資源的程序應該使用類來管理那些資源.使用類管理分配和回收可以保證如果發生異常就釋放資源.

7.  auto_ptr是接收一個類型形參的模板,它爲動態分配的對象提供異常安全
  auto_ptr
只能用於管理從new返回的一個對象,它不能管理動態分配的數組.正如我們所見,auto_ptr被複制或者賦值時,有不尋常的行爲,因此不能將auto_ptr存儲在標準庫容器類型中.
  auto_ptr對象只能保存一個指向對象的指針,並且不能用於指向動態分配的數組,使用auto_ptr對象指向動態分配的數組會導致未定義的運行時行爲.
  
■ 爲異常安全的內存分配使用auto_ptr(它能在任何時候釋放內存
)
  
■ auto_ptr是可以保存任何類型指針的模板

  
■ 將auto_ptr綁定到指針:(接收指針的構造函數爲explicit,即必須使用初始化的直接形式來創建auto_ptr對象
  
■ auto_ptr對象的複製和賦值具有破壞性操作
  auto_ptr
和內置指針對待賦值和複製有非常關鍵的重要區別.當複製auto_ptr對象或者將它的值賦值給其他的對象時,將基礎對象的所有權從原來的auto_ptr對象轉給副本,原來的auto_ptr對象重置爲未綁定狀態.
  
另外,與其他賦值後者複製不同,auto_ptr的賦值和複製改變右操作數,因此賦值的左右操作數必須是可以修改的左值
.
  
注意:因爲複製和賦值是破壞性操作,所以不能將auto_ptr對象存儲在標準容器中.標準庫的容器類型要求在複製或賦值之後兩個對象相等,auto_ptr不滿足這一要求,如果將ap2賦給ap1,則賦值之後ap1!=ap2,複製也類似
.
  
■ 如果要檢查指針是否未綁定,可以在條件中直接測試指針,效果是確定指針是否爲0,相反,不能直接測試auto_ptr對象
.
     auto_ptr
類型沒有定義可用作條件的類型的轉換,相反,要測試auto_ptr對象,必須使用它的get成員,該成員返回包含在auto_ptr對象中的基礎指針(可將get後的值和0比較
).
   
注意:應該只用get詢問auto_ptr對象或者使用返回的指針值,不能用get作爲創建其他auto_ptr對象的實參
.   
  
 auto_ptr對象與內置指針的另一個區別是:不能直接將一個地址(或者其他指針)賦給auto_ptr對象
.
          p_auto = new int(1024);
相反必須使用reset函數來改變指針
:
       if(p_auto.get())
       {
           *p_auto = 1024;
       }
       else
       {
           p_auto.reset(new int(1024));
       }
注意:調用auto_ptr對象的reset函數時,在將auto_ptr對象綁定到其他對象之前,會刪除auto_ptr對象所指的對象(如果存在),但是,正如自身賦值是沒有效果的一樣,如果調用該auto_ptr對象已經保存的同一指針的reset函數,也沒有效果,不會刪除對象.

8.  異常說明指定,如果函數拋出異常,被拋出異常將是包含在該說明中的一種,或者是從列出的異常中派生的類型.
   
使用auto_ptr的限制
:
   
● 不要使用auto_ptr對象保存指向靜態分配對象的指針,否則,auto_ptr對象本身撤銷的時候,它將試圖刪除指向非動態分配對象的指針,導致未定義的行爲
.
   
● 永遠不要是有那個兩個auto_ptr對象指向同一個對象,導致這個錯誤的一種明顯方式是,使用同一指針來初始化或者reset兩個不同的auto_ptr對象,另一種導致這個錯誤的微妙的方式可能是,使用一個auto_ptr對象的get函數的接過來初始化或者reset另一個auto_ptr對象
.
   
● 不要使用auto_ptr對象保存指向動態分配數組的指針.auto_ptr對象被刪除時,它釋放一個對象它使用普通delete操作符,而非delete[]操作符

   
● 不要將auto_ptr對象存儲在容器中.容器要求所保存的類型定義複製和賦值操作符,是它們表現得類似於內存類型的操作符:在複製(或者賦值)之後,兩個對象必須具有相同值,auto_ptr不具備這個特性.

9.  如果一個函數聲明沒有指定異常說明,則該函數可以拋出任意類型的異常.
  
如果函數拋出了沒有在其異常說明中列出的異常,就調用標準庫函數unexpected.默認情況下,unexpected函數調用terminal函數,該函數會終止程序

  
在編譯的時候,編譯器不能也不會試圖驗證異常說明.
  
基類中虛函數異常說明,可以與派生類中對應虛函數的異常說明不同.但是派生類虛函數的異常說明必須與對應基類虛函數的異常說明同樣嚴格,或者比基類更嚴格
.
  
在另一指針初始化帶異常說明的函數的指針,或者將後者賦值給函數地址的時候,兩個指針的異常說明不必相同.但是.源指針的異常說明必須至少與目標指針一樣嚴格.

10. 命名空間可以在全局作用域或者其他作用域的內部定義,但是不能在函數或類內部定義.需要注意的是:命名空間不能以分號結束.
  
在命名空間中定義的名字可以被命名空間中的其他成員直接訪問,命名空間外部的代碼必須指出名字定義在哪個命名空間中
.
  
命名空間由它的分離定義部分的總和構成,命名空間是累積的
.
  
可以用於管理自己的類和函數定義相同的方法來組織命名空間
:
    
● 定義類的命名空間成員,以及作爲類接口一部分的函數聲明與對象聲明,可以放在頭文件中,使用命名空間成員的文件可以包含這些頭文件
.
    
● 命名空間成員的定義可以放在單獨的源文件中
.
  
定義多個不相關的類型的命名空間應該使用分離的文件,表示該命名空間定義的每個類型
.
  
不能在不相關的命名空間中定義成員
.
  
全局命名空間使用來引用其成員(類似於VC中的Win32 API)

11. 未命名的命名空間與其他命名空間不同,未命名的命名空間的定義局部於特定文件,從不跨越多個文件
  
如果在文件的最外層作用域中定義未命名的命名空間,那麼,未命名的命名空間中的名字必須與全局作用域中的定義的名字不同.
  
如果頭文件定義了未命名的命名空間,那麼在每個包含該頭文件的文件中,該命名空間中的名字將定義不同的局部實體
.
  C++
不贊成文件靜態聲明.不贊成的特徵是在未來版本中可能不支持的特徵.應該避免文件靜態而使用未命名的命名空間代替.

12. 命名空間頭文件的使用:除了在函數或者其他作用域內部,頭文件不應該包含Using指示或Using聲明.在頂級作用域包含Using指示或Using聲明的頭文件,具有將改名字注入包含該頭文件的文件中的效果.頭文件應該只定義作爲其接口的一部分的名字,不要定義在其實現中使用的名字.
  
命名空間別名
:namespace primer = cplusplus_primer;
  
用非常相似的方式確定類成員定義中使用的名字,只有一個重要的區別:如果名字不是局部於成員函數,就試着在查找更外層作用域之前在類成員中確定名字
.
  
可以從函數的限定名推斷出查找名字是所檢查作用域的次序,限定名以相反次序指出被查找的作用域
.
接接受類類型形參(或類類型指針及引用)的函數(包括重載操作符),以及魚類本身定義在同一命名空間中的函數(包括重載操作符),在用類類型對象(或類類型的引用或者指針)作爲實參的時候是可見的.說白了意思就是,在函數的參數中如果有該函數所屬類,那麼該函數可以不加限定名(類名
).
  
如果類在命名空間內部定義,則沒有另外聲明的友元函數在同一命名空間中聲明(個人註解:在本書的翻譯中,有一些地方是很難理解的,或者可以說翻譯的很牽強,爲了讓讀者更容易的理解,我一般都會用自己的話來解釋它).他的意思就是說,如果類在命名空間內部定義,此時該類的友元函數在使用時可以不加限定.此種情況有點類似於上面說的那種情況,因爲友元函數參數中已經有了限定名,所以該函數中可以不需要
.
還是同樣舉書上一個例子吧
:

13. 如果Using聲明在已經有同名且帶相同形參表中的函數的作用域中引入函數,Using聲明出錯.否則Using定義給定名字的另一個重載實例.效果是增大候選函數集合.
  
如果命名空間函數與命名空間所在的作用域中聲明的函數同名,就將命名空間成員加到重載集合中.

14. 在命名空間內部聲明模板影響着怎樣聲明模板特化:模板的顯示特化必須在定義通用模板的命名空間聲明,否則該特化將於它所特化的模板不同名.
  
有兩種定義特化的方式:一種是重新打開命名空間並加入特化的含義,可以這樣做是因爲命名空間定義是不連續的.或者可以用於在命名空間定義外部定義命名空間成員相同的方式來定義特化:使用由命名空間名字限定的模板定義特化
/
  
爲了提供命名空間中所定義模板的自己的特化,必須保證在包含原始模板定義的命名空間中定義特化.

15. 多重繼承與虛擬繼承:
1). 
派生類的對象包括構造和初始化它的所有基類子對象
.
2). 
構造函數初始化式只能控制用於初始化基類的值不能控制基類的構造次序.基類構造函數按照本身的構造函數在類派生列表中的出現次序調用
.
    
構造函數調用次序既不受構造函數初始化列表中出現的基類的影響,也不受基類在構造函數初始化列表中的出現次序的影響.

16. 對於多重繼承,派生類的指針或者引用可以轉化爲其任意基類的指針或者引用.
  
當一個類有多個基類的時候,通過所有直接基類同時進行名字查找.多重繼承的派生類有可能從兩個或者多個基類成成同名成員,對該成員不加限定的使用是具有二義性的.

17. C++,通過使用虛繼承解決這列問題.虛繼承是一種機制,類通過虛繼承指出它希望共享其虛基類的狀態.在虛繼承下,對給定虛基類,無論該類在派生層次中作爲虛基類出現多少次,只繼承一個共享的基類子對象.共享的基類子對象稱爲虛基類.
  
指定虛派生隻影響了從指定了虛基類的類派生類.除了影響派生類自己的對象之外,他也是關於派生類與自己的未來派生類的關係的一個陳述.virtual說明符陳述了後代派生類中共享指定基類的單個實例的願望
.
  
可以無二義性的直接訪問共享虛基類中的成員.同樣,如果只沿着一個派生路徑重新來自虛基類的成員,則可以直接訪問該重定義成員.在非虛派生情況下,兩種訪問都可能是二義性的
.
  
假定通過多個派生路徑繼承名爲x的成員,則有下面三種可能性
:
  
■ 如果在每個路徑中x表示同一虛基類成員,則沒有二義性,因爲共享該成員的單個實例
.
  
■ 如果在某個路徑中x是虛基類的成員,則在另一路徑中x是後代派生類的成員,也沒有二義性---特定派生類實例的優先級高於共享虛基類實例

  
■ 如果沿每個繼承路徑x表示後代派生類的不同成員,則該成員的直接訪問是二義性的.(像非虛多重繼承層次一樣,這種二義性最好用在派生類中提供覆蓋實例的類來解決.

爲了避免在應用於虛基類的時候可能出現的多次初始化基類的情況,我們從具有虛基類的類繼承的類對初始化進行特殊處理:由最底層派生類的構造函數初始化虛基類.
  
無論虛基類出現在繼承層次中任何地方,總是在構造非虛基類之前構造虛基類.






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