Tip 20. Grok Visual Mode


Tip 21. Define a Visual Selection

從普通模式切回模式:

v 啟動字符的可視模式

V 啟動字行的可視模式

&ltC-v&gt 啟動塊狀的可視模式

gv 重選上次所選擇的區域



在可視模式:

&ltEsc&gt / &ltC-[&gt 切回普通模式

o 切換高亮區的活動端

silverfoxkkk 發表在 痞客邦 留言(0) 人氣()

Tip 13. Make Corrections Instantly from Insert Mode

在插入模式下:

&ltC-h&gt 刪除前一個字符

&ltC-w&gt 刪除前一個 world

&ltC-u&gt 刪除至行首

silverfoxkkk 發表在 痞客邦 留言(0) 人氣()

Tip 7. Pause with Your Brush Off the Page


Tip 8. Chunk Your Undos

silverfoxkkk 發表在 痞客邦 留言(0) 人氣()

Tip 1. Meet the Dot Command


Tip 2. Don't Repeat Yourself

利用 A 取代 $a

C 取代 c$

s 取代 cl

S 取代 ^C

I 取代 ^i

A 取代 $a

o 取代 A

O 取代 ko

silverfoxkkk 發表在 痞客邦 留言(0) 人氣()



3, Class Templates

相同於 function templates,class templates 也可藉由參數化手段,來表現一整個族群的 classes。



3.2

必須將具體型別當作 template arguments 傳入,才能使用 class template;接著該 class template 會以被指定的那些型別,由 compiler 加以 instantiate。

在 class template 中,只有被實際呼叫使用的 function,才會被 instantiate。




3.3 Specializations

可以針對某些特定的型別,對 class templates 進行 total specialize 或 partially specialize。




3.5 Default Template Arguments

可以為 template parameters 定義預設值,稱為 default template arguments。


silverfoxkkk 發表在 痞客邦 留言(0) 人氣()



2, Function Templates

所謂 function templates,是指藉由參數化手段,來表現一整個族群的 functions。



2.1.2

一般而言,templates 不會被 compile 成能夠處理任意型別的單一 entity,而是為每個特定型別,被 compile 成多個個別 instance。

以具體型別替代 template parameters 的過程,稱為 instantiation。




2.2 Argument Deduction

在 template parameters 進行 argument deduction 時,並不允許自動型別轉換。




2.4 Overloading

function templates 可以被 overloading,在不同的 overloading 形式之間,最好只存在絕對必要的差異。


silverfoxkkk 發表在 痞客邦 留言(0) 人氣()



一、Accustoming Yourself to C++



01、 View C++ as a federation of language.

將 C++ 視為一個由相關語言組成的聯邦語言,其主要的次語言有四種:

C、Object-Oriented C++、Template C++、STL。




02、 Prefer const, enum and inline to #define.

對於單純常數,最好以 const 物件或 enum 取代 #define。

對於類似 function 的 macro,最好改用 inline function 取代 #define。




03、 Use const whenever possible.

將某些東西宣告為 const,可以幫助 compiler 偵測出錯誤用法;const 可被施加於任何作用域內的 object、function parameter、function return type、member function 本體。

compiler 強制實施 bitwise constness ,但在編寫程式時,應該使用 conceptual constness。

兩個 member function,如果只有 constness 不同,仍然可以被 overload;而當 const 和 non-const member function 有著實質相等的實作時,令 non-const 版本呼叫 const 版本可避免程式碼重複。




04、 Make sure that objects are initialized before they're used.

為內建型別進行手工初始化,因為 C++ 不保證初始化它們。

constructor 最好使用 member initialization list,其排列次序應該和宣告次序相同。

為了避免「跨編譯單元之初始化次序」問題,以 local static objects 來取代 non-local static objects。






二、Constructors, Destructors, and Assignment Operators




05、 Know what functions C++ silently writes and calls.

compiler 可以暗自為 class 產生: default constructor、copy constructor、copy assignment operator 及 destructor;但只有在這些 function 被呼叫時,它們才會被 compiler 產生出來。

但如果 user 有自行宣告 constructor,default constructor 且只有 default constructor 不會被自動產生。




06、 Explicitly disallow the use of compiler-generated functions you do not want.

為駁回 compiler 自動提供的機能,可將相應的 member function 宣告為 private 並且不予實作;又或是繼承像 Uncopyable 這樣的 base class。




07、 Declare destructors virtual in polymorphic base classes.

polymorphic base classes 應該宣告一個 virtual destructor,如果 class 帶有任何 virtual function,也應該擁有一個 virtual destructor。

Classes 的設計目的如果不是為了作為 base class,或不是具備 polymorphism,就不應該宣告 virtual destructor。




08、 Prevent exceptions from leaving destructors.

在 destructor 中,絕對不要吐出 exception,如果其呼叫的 function 可能拋出 exception,則吞下它們或結束程式。

如果要讓使用者可以在執行期間對 function 所拋出的 exception 作處理,則 class 應該提供一個普通的 function 執行該操作,讓使用者在非解構式中使用。




09、 Never call virtual functions during constructor or destruction.

在 constructor 及 destructor 中不要呼叫 virtual function,因為這類的呼叫從不會下降至 derived class。




10、 Have assignment operators return a reference to *this.

令 assignment operator 傳回一個 reference to *this,以實現 chain of assignments。




11、 Handle assignment to self in operator=.

確保當 object 執行 self assignment 時,operator= 會具備「自我賦值安全性」及「exception safety」,其中技術包括:identity test、精心排列的述句順序以及 copy and swap。

確定操作一個以上的 object 的任何 function,在其中多個 object 是同一個物件時,也能正常執行。




12、 Copy all parts of an object.

Copying function (就是 copy constructor 及 copy assignment operator) 應該確保複製「object 的所有 data member」及「所有 base class 內的 data member」。

不要嘗試以某個 copying function 呼叫另一個 copying function,而是應該就共同機能放進第三個 function 中,並由兩個 copying function 呼叫。






三、Resource Management



13、 Use objects to manage resources

為了防止 memory leak,儘量使用 RAII(Resource Acquisition Is Initialization) 物件,它會在 constructor 中獲得資源,並在 destructor 中釋放資源。

兩個常被使用的 RAII class 是:tr1::shared_ptr 及 auto_ptr;但 tr1::shared_ptr 是較佳的選擇。





14、 Think carefully about copying behavior in resource-managing classes.

複製 RAII Object 必須一併處理它所管理的資源,而資源的 copying 行為將決定 RAII Object 的 copying 行為。

常見的 RAII class copying 行為是:抑制 copying、使用 reference counting。




15、 Provide access to raw resources in resource-managing classes.

APIs 往往要求存取原始資源,所以每個 RAII class 都應該提供一個「取得其管理之資源」的方式。

對原始資源的存取可能經由顯示轉換或隱式轉換,一般而言,顯示轉換較為安全。



// type conversion operator

ex: operator int () const { return f; } // implicit conversion function




16、 Use the same form in corresponding uses of new and delete.

如果在 new 算式中使用 [],必須也在相應的 delete 算式中使用 [],如果在 new 算式中不使用 [],一定也不要在相應的 delete 算式中使用 []。




17、 Store newed objects in smart pointers in standalone statements.

以獨立述句將 newed object 儲存於 smart pointer 內,以避免有 exception 被拋出,而造成資源洩漏。






四、Designs and Declarations



18、 Make interfaces easy to use correctly and hard to use incorrectly.

要在 interface 中達成容易被使用,不易被誤用的性質。

「促進正確使用」的辦法包括 interface 的一致性,以及與內建型別的行為相容。

「阻止誤用」的辦法包話建立新型別、限制型別上的操作、束縛物件值及消除使用者的資源管理責任。

tr1::shared_ptr 支援 custom deleter,可防範 "cross-DLL problem",也可被用來自動解鎖(mutex,詳見條款14)。




19、 Treat class design as type design.

Class 的設計就是 type 的設計,在定義一個新的 type 之前,需考慮本條款的所有主題。




20、 Prefer pass-by-reference-to-const to pass-by-value.

儘量以 pass-by-reference-to-const 取代 pass-by-value,前者通常較有效率,並可避免 slicing problem。

但對於內建型別及 STL 的迭代器及函式物件,pass-by-value 往往比較適當。




21、 Don't try to return a reference when you must return an object.

絕對不要回傳一個指向 local stack object 的 pointer 或 reference 。

也不要回傳一個指向 heap-allocated object 的 reference 。

也不要回傳一個指向可能同時被操作的 local static object 的 pointer 或 reference 。




22、 Declare data members private.

切記將成員變數宣告為 private,這將賦予存取資料的一致性、可細微劃分存取控制、並提供 class 的充分實作彈性。

protected 並不比 public 更具封裝性。




23、 Prefer non-member non-friend functions to member functions.

寧可拿 non-member non-friend function 取代 member function,這樣做可以增加封裝性、package flexibility 和機能擴充性。

namespace 和 class 不同,前者可跨越多個源碼檔,而後者不能。




24、 Declare non-member functions when type conversions should apply to all parameters.

如果需要為某個 function 作好其所有 parameters 都可能進行型別轉換的準備,那麼這個 function 必須是個 non-member function。




25、 Consider support for a non-throwing swap.

當 std::swap() 對自訂型別效率不高時,提供一個 swap() member function,並確保這個 function 不拋出 exception。

如果提供一個 member swap(),也該提供一個 non-member swap() 用來呼叫前者,對於 class ,也需要 specialize std::swap。

呼叫 swap() 時,應針對 std::swap 使用 using 宣告式,然後不帶任何 namespace 呼叫 swap()。

為了「使用者定義型別」而對 std templates 進行 totally specialize 是好的,但不要嘗試在 std 內加入對 std 而言是全新的東西。



total template specialization vs. partially template specialization






五、Implementations



26、 Postpone variable definitions as long as possible.

儘可能延後變數定義式的出現,可以增加程式的清晰度及程式效率。




27、 Minimize casting.

儘量避免轉型,特別是在注重效率的程式碼中避免 dynamic_cast;如果有個設計需要轉型,試著發展無需轉型的替代設計。

如果轉型是必要的,試著將它隱藏在某個函式背後,讓使用者可以隨時呼叫該函式,而不需將轉型放進他們的程式碼內。

寧可使用 C++-style 轉型,不要使用舊式轉型,前者容易辨識,且有著分門別類的職掌。




28、 Avoid returning "handles" to object internals.

避免傳回 handle 指向 object 內部,將可增加封裝性、幫助 const member 的行為像個 const、並將發生 dangling handle 的可能性降低。




29、 Strive for exception-safe code.

exception-safe function 即使發生 exception 也不會洩漏資源或允許任何資料結構敗壞,這樣的 function 分成三種保證:基本型、強烈型、nothrow 型。

「強烈保證」往往能以 copy and swap 實作出來,但並非對所有 function 都可實現或具備實現意義。

function 所提供的「exception safety 保證」最高只等於其所呼叫之各個 function 的「exception safety 保證」中的最弱者。




30、 Understand the ins and outs of inlining.

如果 inline function 的本體很小,則 compiler 針對「function 本體」所產生的碼可能比針對「function call」所產生的碼更小。

inline function 無法隨著程式庫的升級而升級。

將大多數 inlining 限制在小型、被頻繁呼叫的 function 身上,這將使得日後的 debug 過程及 binary upgradability 更容易,也可使潛在的程式膨脹問題最小化,並使程式的速度提升機會最大化。




31、 Minimize compilation dependencies between files.

支持「compilation dependency 最小化」的一般構想是:相依於宣告式,不要相依於定義式。

奠基於上述構想的兩個手段是 Handle class 及 Interface class。

程式庫 header file 應該以「完全且僅有宣告式」(full and declaration-only forms) 的型式存在。






六、Inheritance and Object-Oriented Design



32、 Make sure public inheritance models "is-a."

「public inheritance」意味 "is-a",適用於 base class 身上的每件事情,也一定適用於 derived class 身上。




33、 Avoid hiding inherited names.

derived class 內的名稱會遮掩 base class 內的所有同名名稱。

而為了讓被遮掩的名稱可以被找到,可以使用 using 宣告式或 forwarding function。




34、 Differentiate between inheritance of interface and inheritance of implementation.

inheritance of interface 和 inheritance of implementation 是不同的;在 public inheritance 中,derived class 總是 inherit base class 的 interface。

pure virtual functions 只具體指定 inheritance of interface。

simple(impure) virtual functions 具體指定 inheritance of interface plus inheritance of a default implementation。

non-virtual functions 具體指定 inheritance of interface plus inheritance of a mandatory implementation。




35、 Consider alternatives to virtual functions.

virtual function 替代方案包括:

  • 使用 non-virtual interface(NVI) 手法,這是 Template Method design pattern 的一種特殊形式,以 public non-virtual member function 包裏較低存取性的 virtual function。

  • 將 virtual function 替換為「function pointer data members」,這是 Strategy design pattern 的一種分解表現形式。

  • 將 virtual function 替換為 tr1::function data members,因而允許任何 callable entity 搭配一個相容於需求的 signature,這也是 Strategy design pattern 的一種形式。

  • 將 virtual function 替換為另一個繼承體系內的 virtual function,這是 Strategy design pattern 的傳統實作手法。



  • 將機能從 member function 移到 class 外部 function,所帶來的缺點之一是:非成員函式將無法存取 class 內的 non-public 成員。

    tr1::function 物件的行為就像一般函式,這樣的物件可以接納「與給定之 target signature 相容」的所有 callable entry。




    36、 Never redefine an inherited non-virtual function.

    non-virtual functions are statically bound,但 virtual functions are dynamically bound。




    37、 Never redefine a function's inherited default parameter value.

    object 的所謂 static type,就是它在程式中被宣告時所採用的型別;而 object 的 dynamic type 則是指「目前所指 object 的型別」。

    default values 都是 statically binding,而 virtual functions - 唯一該 override 的東西 - 卻是 dynamically binding。




    38、 Model "has-a" or "is-implemented-in-terms-of" through composition.

    當 composition 發生於 application domain,所表現出來的是 has-a 的關係。

    而當 composition 發生於 implementation domain,則是表現出 is-implemented-in-terms-of 的關係。




    39、 Use private inheritance judiciously.

    private inheritance 意味 is-implemented-in-terms-of,它通常不如 composition 來得好,但是當 derived class 需要存取 protected base class 的 member,或需要重新定義 inherit 而來的 virtual function 時,這樣設計是合理的。

    private inheritance 可能造成 empty base optimization,對致力於「物件尺寸最小化」的開發著而言,可能很重要。





    40、 Use multiple inheritance judiciously.

    multiple inheritance 較 single inheritance 為複雜,且可能導致 ambiguity 及對 virtual inheritance 的需要。

    virtual inheritance 會增加大小、速度、初始化及賦值的複雜度等等,在 virtual base classes 不帶任何資料的情況,將是最具實用價值的情況。

    multiple inheritance 具有合理應用的情況,其中之一是對一個 interface class 實作 public inheritance ,對另一個協助實作的 class 進行 private inheritance。






    七、Templates and Generic Programming



    41、 Understand implicit interfaces and compile-time polymorphism.

    classes 和 templates 都支援 interfaces 和 polymorphism。

    對 class 而言,interfaces 為 explicit,以 function signatures 為中心, polymorphism 則是透過 virtual functions 發生於 run-time。

    對 template 而言,interfaces 為 implicit,奠基於 valid expressions, polymorphism 則是透過 template instantiation 和 function overloading resolution 發生於 compilation。




    42、 Understand the two meanings of typename.

    宣告 template 參數時,前綴關鍵字可以是 class 也可以是 typename。

    nested dependent type names 必須使用關鍵字 typename 來標識 ,除非是在 base class lists 或 member initialization list 內。




    43、 Know how to access names in templatized base classes.

    可在 derived class template 內透過 "this->" 指涉 base class template 內的 member name,或利用 using declaration,又或藉由 explicit base class qualification,來達到使用 base classes 內的 member name。




    44、 Factor parameter-independent code out of templates.

    Template 會生成多個 classes 和 functions,所以任何 template 程式碼都不該與某個造成膨脹的 template 參數產生相依關係。

    因非型別模板參數(non-type template parameters) 而造成的程式膨脹,往往可消除,作法是以 function parameters 或 class data members 取代 template parameters。

    因型別參數(type parameters) 而造成的程式膨脹,往往可降低,作法是讓帶有完全相同二進制表述 (identical binary representation) 的具現型別 (instantiation type) 共享實作碼。




    45、 Use member function templates to accept "all compatible types."

    請使用 member function template 生成「可接受所有相同型別」的 function。

    如果宣告 member template 用於「generalized copy constructor」或「generalized assignment operator」,還是得需要宣告正常的 copy constructor 及 copy assignment operator。




    46、 Define non-member functions inside templates when type conversions are desired.

    當在編寫一個 class template,而它所提供之「將此 template 相關的」function 支援「所有參數之隱式型別轉換」時,將那些 function 定義為「class template 內部的 friend function」。




    47、 Use traits classes for information about types.

    traits classes 使得「型別相關資訊」在 compile 期可用,以 template 和 partial template specialization 完成實作。

    整合 overloading 後,traits classes 有可能在 compile 期間,就對型別執行 if ... else 測試。




    48、 Be ware of template metaprogramming.

    template metaprogramming(TMP, 模板超編程) 可將工作由 run-time 移往 compile-time,因而得以實現早期錯誤偵測和更高的執行效率。

    TMP 可被用來生成「植基於政策選擇組合」(based on combinations of policy choices) 的客戶訂製碼,也可用來避免生成對某些特殊型別並不適合的碼。






    八、Customizing new and delete



    49、 Understand the behavior of the new-handler.

    set_new_handler 允許使用者指定一個 function,在記憶體配置無法獲得滿足時被喚起。

    nothrow new 是一個頗為侷限的工具,只適用於記憶體配置,後續的 constructor 呼叫還是可能拋出 exception。




    50、 Understand when it makes sense to replace new and delete.

    有許多理由需要寫個自定的 new 及 delete,包括改善效能、對 heap 運用錯誤進行除錯、蒐集 heap 使用資訊…等。




    51、 Adhere to convention when writing new and delete.

    operator new 應該內含一個無窮迴圈,並在其中嘗試 allocate memory,如果無法滿足需求,就該呼叫 new-handler;operator new 也應該有能力處理 0 byte 申請;若是 class 專屬版本則還應該處理「比正確 size 更大的申請」。

    operator delete 應該在收到 null pointer 時,不作任何事;若是 class 專屬版本則還應該處理「比正確 size 更大的申請」。




    52、 Write placement delete if you write placement new.

    當寫一個 placement operator new,也須確定寫出對應的 placement operator delete,以免發生 memory leak。

    如果宣告了 placement new 及 placement delete,需多加一些手法,避免遮掩了它們的正常版本。







    九、Miscellany



    53、 Pay attention to compiler warnings.

    努力在 compiler 的最嚴苛警告層級下爭取「無任何 warning message」。

    不要過度依賴 compiler 的 warning 能力,不同的 compiler 對待事情的態度並不相同。




    54、 Familiarize yourself with the standard library, including TR1.

    C++ standard library 主要機能由 STL, iostream, locale 組成,並包含 C99 standard library。

    TR1 添加了 smart pointer, 一般化 function pointer, hash-based container, regular expression 及另外 10 個 components 的支援。

    TR1 本身只是一份規格,為了得到 TR1 提供的好處,還需要一份實物,一個好的實物來源是 Boost。




    55、 Familiarize yourself with Boost.

    Boost 是一個社群,也是一個網站,致力於免費、源碼開放、同僚覆審的 C++ library development,Boost 在 C++ 標準化過程中扮演深具影響力的角色。

    Boost 提供許多 TR1 組件實作品,以及其它許多函式庫。








    silverfoxkkk 發表在 痞客邦 留言(0) 人氣()



    九、Miscellany

    本章的第一個條款,強調不可輕忽 compiler warning messages。

    第二個條款綜覽 C++ standard library,其中涵蓋由 TR1 引進的重大新機能。

    第三個條款則綜覽 Boost,它是一個重要的 C++ 泛用型網站。



    53、Pay attention to compiler warnings.

    舉個例子,下列是多多少少會發生的錯誤:

    class B {
    public:
    virtual void f() const;
    };

    class D : public B {
    public:
    virtual void f();
    };



    於是 compiler 可能會出現 warning message:

    warning: D::f() hides virtual B::f()



    這個 warning message 說明在 B 中的 f 並未在 D 中被重新宣告,而是整個被遮掩了,原因詳見條款 33。

    如果忽略這個 compiler warning,則可能導致錯誤的程式行為,然後是更多的除錯行為,只為了找出 compiler 早就偵測出來的事情。



    warning message 跟 compiler 是相依的,不同的 compiler 有不同的標準。




    努力在 compiler 的最嚴苛警告層級下爭取「無任何 warning message」。

    不要過度依賴 compiler 的 warning 能力,不同的 compiler 對待事情的態度並不相同。





    54、Familiarize yourself with the standard library, including TR1.

    TR1 代表 "Technical Report 1",而 TR1 提供的機能幾乎對每一個程式庫和每一種應用都帶來利益。



    C++98 列入的 C++ standard library 有:

    STL:包含 container, iterator, algorithm, function object, container adapter, function object adapter。
    iostream:包含使用者自定緩衝功能、國際化 I / O,及預先定義好的物件 cin, cout, cerr 及 clog。
    國際化支援:包括多區域能力。
    數值處理:包括複數模板和純數值陣列。
    exception hierarchy:包括 base class exception, derived class logic_error 和 runtime_error,及更深繼承的各個 class。
    C89 standard library。



    TR1 詳細敘述了 14 個新 components,統統放在 std namespace 內,更正確的說是在其 nested namespace tr1 內:

    std::tr1



    組件實例:

  • smart pointer:tr1::shared_ptr 及 tr1::weak_ptr,詳見條款 13

  • tr1:function:此物的以表示任何 callable entity,只要其 signature 符合標的,詳見條款 35

  • tr1:bind:詳見條款 35



  • Hash table:

  • Regular Expression:

  • Tuples:

  • tr1::array:

  • tr1::mem_fn:

  • tr1:;reference_wrapper:

  • random number:

  • 數學特殊函數:

  • C99 相容擴充:



  • Type traits:

  • tr1::result_of:





  • C++ standard library 主要機能由 STL, iostream, locale 組成,並包含 C99 standard library。

    TR1 添加了 smart pointer, 一般化 function pointer, hash-based container, regular expression 及另外 10 個 components 的支援。

    TR1 本身只是一份規格,為了得到 TR1 提供的好處,還需要一份實物,一個好的實物來源是 Boost。





    55、Familiarize yourself with Boost.

    Boost 是一個 C++ 開發者集結的社群,也是一個可自由下載的 C++ 程式庫群,網址是 http://boost.org。



    Boost 的第一個特點:Boost 是由 C++ 標準委員會成員創設,因此 Boost 成員和委員會成員有很大的重疊。

    第二:Boost 接納程式庫的過程是以 public peer review。



    Boost 是一個社群,也是一個網站,致力於免費、源碼開放、同僚覆審的 C++ library development,Boost 在 C++ 標準化過程中扮演深具影響力的角色。

    Boost 提供許多 TR1 組件實作品,以及其它許多函式庫。

    silverfoxkkk 發表在 痞客邦 留言(0) 人氣()



    八、Customizing new and delete

    對比於 Java 和 .NET 的內建「垃圾回收能力」,C++ 對記憶體管理的純手工法看起來有點老氣,但許多苛刻的系統程式開發人員之所以選擇 C++,就是因為它允許手工管理記憶體,這樣的開發人員研究並學習軟體使用記憶體的行為特徵,然後修改配置和歸還工作,以求獲得其所建置的系統最佳效率。



    這麼做的前提是,瞭解 C++ 記憶體管理常式的行為,兩個主角是 allocation and deallocation routines,也就是 operator new 和 operator delete,配角是 new-handler,這是當 operator new 無法滿足的記憶體需求的所喚起的 function。



    multi-thread 的記憶體管理下,由於 heap 是一個可被改動的全域性資源,因此充斥著發狂存取這一類資源的 race condition 出現機會,本章中多個條款提及使用可改動之 static 資料,這總是會令 thread-aware programmer 高度警戒如坐針氈,因為如果沒有適當的 synchronization,一旦使用 lock-free algorithm,或沒有精心防止 concurrent access 時,呼叫記憶體常式可能很容易導致管理 heap 的資料結構內容敗壞。



    另外,operator new 和 operator delete 只適合用來配置單一物件, Arrays 所用的記憶體由 operator new[] 配置出來,並由 operator delete[] 歸還,而除非特別表示,否則本章中每件關於 operator new 和 operator delete 的事,也都適用於 operator new[] 和 operator delete[]。




    49、Understand the behavior of the new-handler.

    當 operator new 無法滿足某一記憶體配置需求時,它會拋出 exception,以前它會傳回 NULL pointer,而某些舊式的 compiler 目前也還這麼做。



    當 operator new 拋出 exception 以反應一個未獲滿足的記憶體需求之前,會先呼叫一個客戶指定的錯誤處理函式,一個所謂的 new-handler (詳見條款 51),為了指定這個「用以處理記憶體不足」的 function,使用者必須呼叫 set_new_handler,這是宣告於 &ltnew&gt 的一個 standard library function:

    namespace std {
    typedef void (*new_handler) ( );
    new_handler set_new_handler(new_handler p) throw();
    }



    new_handler 是個 typedef,定義出一個 pointer 指向 function,該 function 沒有參數也不傳回任何東西,set_new_handler 則是「獲得一個 new_handler 並傳回一個 new_handler」的 function,set_new_handler 宣告式尾端的 "throw()" 表示該 function 不能拋出任何 exception,詳見條款 29。



    set_new_handler 的參數是個 pointer,指向 operator new 無法配置足夠記憶體時該被喚起的 function,而反回值也是個 pointer,指向被呼叫前正在執行但馬上就要被替換的那個 new-handler function。



    void outOfMem() {
    std::cerr << "Unable to satisfy request for memory\n";
    std::abort();
    }

    int main() {
    std::set_new_handler(outOfMem);
    int* pBigDataArray = new int[100000000L];
    ...
    }



    就上例而言,如果 operator new 無法配置足夠的空間, outOfMem 就會被喚起,於是程式發出一個訊息之後夭折(abort)。



    當 operator new 無法滿足記憶體申請時,它會不斷呼叫 new-handler function,直到找到足夠記憶體,引起反覆呼叫的程式碼須參考條款 51,一個設計良好的 new-handler function 必須做到:

  • 讓更多記憶體可被使用:

  • 安裝另一個 new-handler:

  • 卸除 new-handler:

  • 拋出 bad_alloc (或衍生自 bad_alloc 的) exception:

  • 不回返:





  • 如果希望可以藉由被配置物屬於哪個 class,來決定以何種方式處理記憶體配置失敗的情況,可以自行實作出這種行為:

    只需令每個 class 提供自己的 set_new_handler 和 operator new 即可:

    其中 set_new_handler 讓使用者可以指定 class 專屬的 new-handler。

    而 operator new 則確保在配置 class 物件記憶體的過程中,以 class 專屬之 new-handler 取代 global new-handler。




    直至 1993 年,C++ 都還要求 operator new 必須在無法配置足夠記憶體時傳回 NULL,但新一代的 operator new 則應該拋出 bad_alloc exception,不過不少 C++ 程式是在 compiler 開始支援新修規格前寫出來的,C++ 標準委員會於是提共另一形式的 operator new,負責供應傳統的「配置失敗便傳回 NULL」行為,這個形式被稱為 "nothrow" 形式:

    class Widget { ... };
    Widget* pw1 = new Widget; // 若配置失敗,拋出 bad_alloc
    if (pw1 == NULL) ... // 這個測試一定失敗
    Widget* pw2 = new (std::nothrow) Widget; // 若配置 Widget 失敗,傳回 NULL
    if (pw2 == NULL) ... // 這個測試可能成功



    nothrow new 對 exception 的強制保證性並不高,算式 "new (std::nothrow) Widget" 發生兩件事,第一,nothrow 版的 operator new 被喚起,用以配置足夠記憶體給 Widget 物件,如果配置失敗則傳回 NULL。

    第二,如果配置成功,Widget constructor 將會被喚起,而在 constructor 中,可能又 new 一些記憶體,而沒人可以再強迫它再次使用 nothrow new。

    結論就是,nothrow new 只能保証 operator new 不拋擲 exception,不保證像 "new (std::nothrow) Widget" 這樣的算式絕不導致 exception。




    set_new_handler 允許使用者指定一個 function,在記憶體配置無法獲得滿足時被喚起。

    nothrow new 是一個頗為侷限的工具,只適用於記憶體配置,後續的 constructor 呼叫還是可能拋出 exception。





    50、Understand when it makes sense to replace new and delete.

    替換編譯器所提供的 operator new 或 operator delete 的三個最常見理由:

  • 用來檢測運用上的錯誤:如果將「new 所得記憶體」 delete 失敗,會導致 memory leaks,若 delete 多次,則導致不確定從為;此外,若編程錯誤將導致資料 "overruns" 或 "underruns";如果可以自行定義 operator new 及 delete,便可配置超額記憶體,以額外空間放置特定的 byte pattern,而在 delete 時,得以檢查 byte pattern 是否原封不動,以判斷是否有 "overruns" 或 "underruns" 的情況發生。

  • 為了強化效能:compiler 所帶的 operator new 及 delete 主要用於一般目的,處理需求包含大塊記憶體、小塊記憶體、大小混合記憶體,但不對特定項目有最佳表現,所以如果對程式的動態記憶體運用型態有深測瞭解的話,通常可以發現,自定版本的 operator new 及 delete 性能會勝過預設版本。

  • 為了收集使用上的統計數據:在自訂義 operator new 及 delete 之前,理當先收集軟體如何使用其動態記憶體,配置區塊的大小分佈如何?壽命分佈如何?及其它相關資訊等。






  • static const int signature = 0xDEADBEEF;
    typedef unsigned char Byte;

    void* operator new(std::size_t size) throw(std::bad_alloc) {
    using namespace std;
    size_t realSize = size + 2 * sizeof(int); // 增加兩個 int

    void* pMem = malloc(realSize); // 呼叫 malloc 取得 memory
    if (!pMem) throw bad_alloc();

    *(static_cast(pMem)) = signature; // 塞入 signature
    *(reinterpret_cast(static_cast(pMem)+realSize-sizeof(int))) =
    signature;

    return static_cast(pMem) + sizeof(int);
    }



    這個 operator new 的缺點主是在於疏忽了身為這個特殊函式所應該具備的「堅持 C++ 規矩」的態度,詳見條款 51。



    另一個比較微妙的主題是 alignment:

    許多 computer architectures 要求特定的型別必須放在特定的記靜勭位址上,如可以會要求指標的位址必須是 four-byte aligned 或 double 的位址必須是 eight-byte aligned,如果沒有奉行,可能導致執行期硬體異常,或效率變差。




    總結一下何時可在「全域性的」或「class 專屬的」基本上,合理替換預設的 operator new 或 operator delete :

  • 為了檢測運用錯誤:

  • 為了蒐集動態配置記憶體之使用統計資訊:

  • 為了增加配置和歸還的速度:

  • 為了降低預設記憶體管理器帶來的空間額外開銷:

  • 為了彌補預設配置器中的 suboptimal alignment:

  • 為了相相關物件成簇集中:

  • 為了獲得非傳統的行為:





  • 有許多理由需要寫個自定的 new 及 delete,包括改善效能、對 heap 運用錯誤進行除錯、蒐集 heap 使用資訊…等。





    51、Adhere to convention when writing new and delete.

    實作一致性 operator new 必得傳回正確的值、記憶體不足時必得呼叫 new-handling function (詳見條款 49)、必須有對付零記憶體需求的準備、還需避免不慎掩蓋正常形式的 new。



    operator new 的回返值則較為單純,如果有能力供應使用者申請的記憶體,則傳回一個 pointer 指向那塊記憶體,如果沒有,則遵循條款 49 所描述的規則,並拋出一個 bad_alloc exception。



    其實 operator new 實際上不只一次嘗試配置記憶體,且在每次失敗後呼叫 new-handling function,這裡假設 new-handling function 也許能夠做某些動作而將 memory 釋放出來,而只有當 new-handling function 的 pointer 為 null 時,operator new 才會拋出 exception。



    按照 C++ 規定,即使客戶要求 0 byte,operator new 也得傳回一個合法指標,下列是個 non-member operator new pseudocode:


    void* operator new(std::size_t size) throw(std::bad_alloc) {
    using namespace std;
    if (size == 0) {
    size = 1;
    }

    while (true) {
    if (配置成功)
    return (a pointer,指向配置來的 memory);
    // 配置失敗:找出目前的 new-handling function
    // 就 multi-thread 而言,還需加上 lock 保護
    new_handler globalHandler = set_new_handler(0);
    set_new_handler(globalHandler);

    if (globalHandler) (*globalHandler) ();
    else throw std::bad_alloc();
    }
    }



    條款 49 談到 operator new 內含一個無窮迴圈,而上述的 pseudocode 明白顯示這個迴圈,而退出此迴圈的方法是:

    配憶體被成功配置或 new-handling function 作了一件描述於條款 49 的事情:

    讓更多 memory 可用、安裝另一個 new-handler、卸除 new-handler、拋出 bac_alloc excpetion、或是承認失敗直接 return。




    寫出訂製型記憶體管理器的一個最常見理由是針對某特定 class 的物件配置行為提供最佳化,卻不是為了該 class 的任何 derived class,也就是說,如果針對 class X 而設計的 operator new,其行為很典型地只為大小剛好為 sizeof(X) 的物件而設計,一旦被繼承,有可能 base class 的 operator new 被呼叫用以配置 derived class 物件:

    class Base {
    public:
    static void* operator new(std::size_t size) throw(std::bad_alloc);
    ...
    };
    class Derived : public Base { // 若 Derived 未宣告 operator new
    ...
    };
    Derived* p = new Derived; // 這裡呼叫的是 Base::operator new



    如果 Base class 專屬的 operator new 並非被設計用來對付上述情況,處理此情勢的最佳作法是將「記憶體申請量錯誤」的呼叫行為改採標準 operator new:

    void* Base::operator new(std::size_t size) throw(std::bad_alloc) {
    if (size != sizeof(Base)) // 如果 size 錯誤,令標準的
    return ::operator new(size); // operator new 處理
    ...
    }




    而 operator delete 的情況更簡單,需要記住的唯一事情就是 C++ 保証「刪除 null pointer 永遠安全」,所以必須兌現這項保証:

    void operator delete(void *rawMemory) throw() {
    // 如果是 null pointer,則 do nothing
    if (rawMemory == 0) return;

    // 現在,歸還 rawMemory 所指的 memory
    }



    而 operator delete 的 member 版本,也只需要多加一個動作檢查刪除數量:

    class Base {
    public:
    static void* operator new(std::size_t size) throw(std::bad_alloc);
    static void operator delete(void* rawMemory, std::size_t size) throw();
    };

    void Base::operator delete(void* rawMemory, std::size_t size) throw() {
    if (rawMemory ==0) return;
    if (size != sizeof(Base)) {
    ::operator delete(rawMemory);
    return;
    }
    // 歸還 rawMemory 所指的 memory
    return;
    }




    如果即將被刪除的物件衍生自某個 base class,而此 base class 欠缺 virtual destructor,則 C++ 傳給 operator delete 的 size_t 數值可能會不正確,所以必須「讓 base class 擁有 virtual destructor」,此外條款 7 也有另外的理由。




    operator new 應該內含一個無窮迴圈,並在其中嘗試 allocate memory,如果無法滿足需求,就該呼叫 new-handler;operator new 也應該有能力處理 0 byte 申請;若是 class 專屬版本則還應該處理「比正確 size 更大的申請」。

    operator delete 應該在收到 null pointer 時,不作任何事;若是 class 專屬版本則還應該處理「比正確 size 更大的申請」。





    52、Write placement delete if you write placement new.

    回憶條款 16, 17 中,一個 new 算式:

    Widget* pw = new Widget;



    共有兩個 function 被呼叫:

    一個是用以配置記憶體的 operator new,另一個是 Widget 的 default constructor。



    假設第一個 function 呼叫成功,第二個 function 卻發出 exception,如此,步驟一的記憶體配置所得必須取消並恢復舊觀,否則會造成 memory leak,而取消步驟一並恢復舊觀的責任就在 C++ 執行期系統身上。



    在執行期系統呼叫步驟一所呼叫的 operator new 的相應 operator delete 版本,前提是必須知道哪一個 operator delete 該被呼叫,如果是擁有正常 signature 的 new 和 delete,則不是問題,因為正常的 operator new 對應於正常的 operator delete:

    void* operator new(std::size_t) throw(std::bad_alloc);
    // global 作用域中的正常簽名式
    void operator delete(void* rawMemory) throw();
    // class 作用域中的正常簽名式
    void operator delete(void* rawMemory, std::size_t size) throw();



    然而當開始宣告非正常形式的 operator new,也就是帶有附加參數的 operator new,「究竟哪個 delete 伴隨這個 new」的問題便浮便了。



    假設寫了一個 class 專屬的 operator new,要求一個 ostream 用來誌記相關配置資訊,同時又寫了一個正常形式的 class 專屬 operator delete:

    class Widget {
    public:
    ...
    static void* operator new(std::size_t size,
    std::ostream& logStream) throw(std::bad_alloc);
    // 非正常形式的 new
    static void operator delete(void* pMemory std::size_t size)
    throw(); // 正常形式的 delete
    ...
    };



    這個設計有問題,但在探討原因之後,先扼要討論若干術語。



    如果 operator new 接受的參數除了一定會有的 size_t 之外,還有其它,這便是所謂的 placement new,因此上述的 operator new 是個 placement 版本,而眾多 placement new 版本中特別有用的一個是「接受一個指標指向該被建構之處」:

    void* operator new(std::size_t, void* pMemory) throw();
    // placement new



    當人們談到 placement new,大多時候所談的是此一特定版本,也就是「唯一額外引數是個 void*」,少數時候才是指接受任何額外引數之 operator new。

    一般性術語 "placement new" 意味帶任意參數的 new ,而另一個術語 "placement delete" 直接衍生自它。



    回到 Widget class 的宣告式:

    Widget* pw = new (std::cerr) Widget;
    // 呼叫 operator new 並傳遞 cerr 為其 ostream 引數,
    // 會在 Widget constructor 拋出 exception 時造成 memory leak




    如果記憶體配置成功,而 Widget constructor 拋出 exception,執行期系統有責任取消 operator new 的配置並恢復舊觀,然而執行期系統無法得知真正被喚起的那個 operator new 如何運作,因此無法取消配置並恢復舊觀,取而代之的是,執行期系統尋找「參數個數和型別都與 operator new 相同」的某個 operator delete。

    如果找到,那就是它的呼叫對象:

    void operator delete(void*, std::ostream&) throw();



    如果找不到,則什麼都不做,這就會造成 memory leak。




    所以規則很簡單:

    如果一個帶額外參數的 operator new 沒有「帶相同額外參數」的對應版 operator delete,那麼當 new 的記憶體配置動作需要取消並恢復舊觀時,就沒有任何 operator delete 會被喚起。

    因此,為了消弭稍早程式碼中的記憶體洩漏,Widget 有必要宣告一個 placement delete,對應於 placement new:

    class Widget {
    public:
    ...
    static void* operator new(std::size_t size,
    std::ostream& logStream) throw(std::bad_alloc);
    // 非正常形式的 new
    static void operator delete(void* pMemory std::size_t size)
    throw(); // 正常形式的 delete
    static void operator delete(void* pMemory std::size_t size,
    std::ostream& logStream) throw();
    // 非正常形式的 delete
    ...
    };




    附帶一提,由於 member function 的名稱會掩蓋其外圍作用域中的相同名稱,詳見條款 33,所以必須小心避免讓 class 專屬的 operator new 掩蓋使用者期望的其它 operator new,如果有一個 base class,其中宣告了唯一一個 placement operator new,則使用者會發現他們無法使用正常形式的 new:

    class Base {
    public:
    ...
    static void* operator new(std::size_t size, std::ostream& logStream)
    throw(std::bad_alloc); // 會遮掩正常的 global 型式
    ...
    };

    Base* pb = new Base; // 錯誤,正式型式的 operator new 被遮掩
    Base* pb = new (std::cerr) Base; // 正確


    同樣道理,derived class 中的 operator new 也會遮蓋 global 版本和繼承而得的 operator new:

    class Derived : public Base {
    public:
    ...
    static void* operator new(std::size_t size, std::ostream& logStream)
    throw(std::bad_alloc); // 重新宣告正常型式的 new
    ...
    };

    Derived* pd = new (std::clog) Derived;
    // 錯誤,Base 的 placement new 被遮掩了
    Derived* pd = new Derived; // 正確



    條款 33中有詳細討論這種名稱遮掩問題,預設情況下,C++ 在 global 作用域內提供以下形式的 operator new:

    void* operator new(std::size_t) throw(std::bad_alloc);
    // normal new
    void* operator new(std::size_t, void*) throw(); // placement new
    void* operator new(std::size_t, const std::nothrow_t&) throw();
    // nothrow new



    在 class 內宣告任保 operator new,都會遮掩上述這些標準形式,除非是刻意要阻止 class 的使用者使用這些形式,否則需確保它們在所生成的任保訂製型 operator new 之外都還可以被使用,而對每個可用的 operator new 也需確定提供對應的 operator delete,如果希望這些 function 有著平常的行為,只要令 class 專屬版本呼叫 global 版本即可。

    完成上述所言的一個簡單作法是,建立一個 base class,內含所有正常形式的 new 和 delete:


    class StandardNewDeleteForms {
    public:
    // normal new / delete
    static void* operator new(std::size_t size) throw(std::bad_alloc) {
    return ::operator new(size);
    }
    static void* operator delete(void* pMemory) throw() {
    ::operator delete(pMemory);
    }
    // placement new / delete
    static void* operator new(std::size_t size, void* ptr)
    throw() {
    return ::operator new(size, ptr);
    }
    static void* operator delete(void* pMemory, void* ptr)
    throw() {
    ::operator delete(pMemory, ptr);
    }
    // nothrow new / delete
    static void* operator new(std::size_t size,
    const std::nothrow_t& nt) throw() {
    return ::operator new(size, nt);
    }
    static void* operator delete(void* pMemory,
    const std::nothrow_t&) throw() {
    ::operator delete(pMemory);
    }
    };



    而凡是想以自定型式擴充標準型式的使用者,可利用 inheritance 及 using 宣告式取得標準型式:

    class Widget : public StandardNewDeleteForms {
    public:
    // 讓這些型式可見
    using StandardNewDeleteForms::operator new;
    using StandardNewDeleteForms::operator delete;
    // 添加一個自定的 placement new
    static void* operator new(std::size_t size,
    std::ostream& logStream) throw(std::bad_alloc);
    // 添加一個自定的 placement delete
    static void operator delete(void* pMemory,
    std::ostream& logStream) throw();
    ...
    };




    當寫一個 placement operator new,也須確定寫出對應的 placement operator delete,以免發生 memory leak。

    如果宣告了 placement new 及 placement delete,需多加一些手法,避免遮掩了它們的正常版本。

    silverfoxkkk 發表在 痞客邦 留言(0) 人氣()



    Function Pointer and Function Object



    Function Pointer

    函式指標的宣告方式如下所示:

    傳回值型態 (*指標名稱) (傳遞參數)

    ex: int (*func)(int, float, string); or int (*func)(int, int, int); ...



    為什麼會需要 function pointer、function object 這種機制呢?

    源於一個很簡單的想法:『為什麼不能將 function 也如同變數一樣傳進另外一個 function 呢?』,C 語言的解決方式是,利用 pointer 指向該 function,將該 pointer 傳入另外一個 function,只要將該 pointer dereference 後,就如同存取原 function 一樣;C# 解決的方式是,將 function 包成 delegate object,傳入另外一個 function;C++ 的解決方式是,利用 class 或 struct 將 function 包成 object,傳入另外一個 function。



    一個很簡單的需求,想個別列出陣列中,所有奇數、偶數、和大於 2 的數字,若使用傳統方式,而不使用 function pointer,則寫法如下:

    #include &ltiostream&gt
    using namespace std;
    void printArrayOdd(int* beg, int* end) {
    while(beg != end) {
    if ((*beg)%2)
    cout << *beg << endl;
    beg++;
    }
    }
    void printArrayEven(int* beg, int* end) {
    while(beg != end) {
    if (!((*beg)%2))
    cout << *beg << endl;
    beg++;
    }
    }
    void printArrayGreaterThan2(int* beg, int* end) {
    while(beg != end) {
    if ((*beg)>2)
    cout << *beg << endl;
    beg++;
    }
    }

    int main() {
    int ia[] = {1, 2, 3};
    cout << "Odd" << endl;
    printArrayOdd(ia, ia + 3);
    cout << "Even" << endl;
    printArrayEven(ia, ia + 3);
    cout << "Greater than 2" << endl;
    printArrayGreaterThan2(ia, ia + 3);
    }



    以功能而言沒有問題,但每個 function 都要做迴圈與判斷,似乎重覆了,而且將來若有新的判斷,又要 copy 整個迴圈,然後改掉判斷式,如果可以將迴圈與判斷式分離,日後如果再有新的判斷式,只要將該判斷式傳進來即可,這就是 function pointer 概念:

    #include &ltiostream&gt
    using namespace std;
    typedef bool (*predicate) (int);

    bool isOdd(int i) {
    return ((i%2) ? true : false);
    }
    bool isEven(int i) {
    return ((i%2) ? false : true);
    }
    bool isGreaterThan2(int i) {
    return i>2;
    }
    // 以 predicate fn 取代 bool (*fn)(int)
    bool printArray(int *beg, int *end, predicate fn) {
    while ( beg != end ) {
    if ( (*fn)(*beg) )
    cout << *beg << "\t";
    ++beg;
    }
    cout << endl << endl;
    }
    int main(int argc, char* argv[])
    {
    int ia[7] = {1, 2, 3, 4, 5, 6, 7};
    int size = sizeof(ia) / sizeof(int);

    cout << "Odd :" << endl;
    printArray(ia, ia+size, isOdd);

    cout << "Even :" << endl;
    printArray(ia, ia+size, isEven);

    cout << "Greater than 2 :" << endl;
    printArray(ia, ia+size, isGreaterThan2);
    }



    關鍵行:

    typedef bool (*predicate)(int);


    宣告了 predicate 這個 function pointer 型別,指向回傳值為 bool,參數為 int 的 function,值得注意的是 (*predicate) 一定要括號刮起來,否則 compiler 會以為是 bool*,或許這個語法很奇怪,但好像也沒其他更好的語法了。

    這個範例將判斷式和迴圈分開,日後若有新的判斷式,只要新增判斷式即可,function pointer 提供了一個型別,讓參數可以宣告 function pointer 型別。



    至於另一種寫法是不需要 typedef 的寫法,也可參考看看:

    bool printArray(int *beg, int *end, bool (*fn)(int)) {
    ...
    }





    Function Object

    function object 也稱為 functor,用 class 或 struct 來實作都可以,不過因為 function object 是利用 constructor 及對 operator() 做 overloading,而這些都是 public 的,所以大多數人選擇直接使用 struct,這樣可以少打 public: 這幾個字:

    #include &ltiostream&gt

    using namespace std;

    template &lttypename T&gt
    struct isOdd {
    bool operator() (T i) {
    return i%2 ? true : false;
    }
    };

    template &lttypename T&gt
    struct isEven {
    bool operator() (T i) {
    return i%2 ? false : true;
    }
    };

    template &lttypename T&gt
    struct greaterThan2 {
    bool operator() (T i) {
    return i > 2;
    }
    };

    template &lttypename T&gt
    struct greaterThanAny {
    T _val;
    greaterThanAny(T n) : _val(n) {}
    bool operator() (T i) {
    return i > _val;
    }
    };

    template &lttypename T, typename U&gt
    void printArray(T beg, T end, U fn) {
    while (beg != end) {
    if (fn(*beg))
    cout << *beg << endl;
    beg++;
    }
    };

    int main() {
    int ia[] = {1, 2, 3};

    cout << "Odd" << endl;
    printArray(ia, ia + 3, isOdd&ltint&gt());

    cout << "Even" << endl;
    printArray(ia, ia + 3, isEven&ltint&gt());

    cout << "Greater than 2" << endl;
    printArray(ia, ia + 3, greaterThan2&ltint&gt());

    cout << "Greater than any" << endl;
    printArray(ia, ia + 3, greaterThanAny&ltint&gt(1));
    }



    使用了 template,不過並非必要,只是顯示 function object 可以搭配 template 使用,而使用的技巧只是將 function 內的東西搬到 operator() 內:

    template &lttypename T&gt
    struct isOdd {
    bool operator() (T i) {
    return i%2 ? true : false;
    }
    }



    而 function object 優於 function pointer 之處,由 C 語言範例可知,若今天需求改變成 greaterThan3 ,則又得再寫一個判斷式了,但因為 function object 是透過 struct 或 class,而 struct 和 class 還有個 constructor 可以運用,所以能藉由 constructor 對 class 做初始化,因此能寫出 greaterThanAny(),大於多少只要從 constructor 帶入即可,而 operator() 的寫法則不變:

    template &lttypename T, typename U&gt
    void printArray(T beg, T end, U fn) {
    while (beg != end) {
    if (fn(*beg))
    cout << *beg << endl;
    beg++;
    }
    };




    Another example about function pointer:

    #include "stdio.h"

    int func_1() {
    printf("> function 1.\n");
    return 1;
    }

    int func_2() {
    printf("> function 2.\n");
    return 2;
    }

    int func_3() {
    printf("> function 3.\n");
    return 3;
    }

    int func_4() {
    printf("> function 4.\n");
    return 4;
    }

    int func_5() {
    printf("> function 5.\n");
    return 5;
    }

    void main() {

    int (*f_ptr[5])() = { func_1, func_2, func_3, func_4, func_5 };
    for (int i = 0; i < 5; i++)
    (*f_ptr[i])();
    }





    Ref: Function Pointer(C)、Delegate(C#)和Function Object(C++)

    silverfoxkkk 發表在 痞客邦 留言(0) 人氣()



    七、Templates and Generic Programming

    C++ template 的最初發展動機是:

    建立「type-safe」的容器,如 vector, list 和 map。

    後來發現 C++ template 有能力完成愈多可能的變化,容器當然很好,但 generic programming 寫出的程式碼和其所處理的物件型別彼此獨立 - 則更好。

    最終發現 C++ template 機制本身是一部完整的 Turning-complete:

    它可以被用來計算任何可計算的值,於是導出了 template metaprogramming,創造出「在 C++ 編譯期內執行並於編譯完成時停止執行」的程式。

    儘管 template 的應用如此廣泛,有一組核心觀念一直支撐著所有以 template 為基礎的 programming,這些觀念就是本章的重點。




    41、Understand implicit interfaces and compile-time polymorphism.

    Object oriented programming 總是以 explicit interface 及 runtime polymorphism 解決問題:

    class Widget {
    public:
    Widget();
    virtual ~Widget();
    virtual std::size_t size() const;
    virtual void normalize();
    void swap(Widget& other);
    ...
    };

    void doProcessing(Widget& w) {
    if (w.size() > 10 && w != someNastyWidget) {
    Widget temp(w);
    temp.normalize();
    temp.swap(w);
    }
    }



    在 doProcessing 的 w:

  • 由於 w 的型別被宣告為 Widget,所以 w 必須支援 Widget 介面,而這個介面,可以在源碼中找到,所以稱為 explicit interface。

  • 由於 Widget 的某些 member function 是 virtual,w 對那些 function 的呼叫將表現出 runtime polymorphism,也就是說將在執行期根據 w 的 dynamic type 來決定究竟要喚起哪個 function。





  • template 及 generic programming 的世界與 Object-oriented 有根本上的不同,在此世界,explicit interface 及 runtime polymorphism 仍然存在,例重要性降低,反倒是 implicit interface 及 compile-time polymorphism 較具有重要性:

    template
    void doProcessing(T& w) {
    if (w.size() > 10 && w != someNastyWidget) {
    T temp(w);
    temp.normalize();
    temp.swap(w);
    }
    }



    在 doProcessing 的 w:

  • w 必須支援哪種介面,是由 template 中執行於 w 身上的操作來決定,從上例看,w 的型別好像必須支援 size(), normalize() 和 swap member function, copy constructor(用以建立 temp), inequality comparison。

  • 凡涉及 w 的任何 function 的呼叫,例如 operator> 和 operator!=,都有可能造成 template 具現化(instantiated),使這些呼叫得以成功,這樣的具現行為發生在編譯期,「以不同的 template 參數具現化 function templates」會導致喚起不同的函式,也就是所謂的 compile-time polymorphism。





  • 通常 explicit interface 由 function 的 signature (也就是 function name, 參數型別、回返型別) 構成:

    class Widget {
    public:
    Widget();
    virtual ~Widget();
    virtual std::size_t size() const;
    virtual void normalize();
    void swap(Widget& other);
    ...
    };



    其 public interface 是由一個 constructor、一個 destructor、function size()、normalize()、swap() 及其參數型別、回返型別、constness 構成,當然也包括 compiler 產生的 copy constructor 及 copy assignment operator。



    implicit interface 就完全不同了,它不奠基於 function signature,而是由有效算式(valid expression)組成:

    template
    void doProcessing(T& w) {
    if (w.size() > 10 && w != someNastyWidget) {
    ...
    }
    }



    T(w 的型別) 的 implicit interface 看來好像有這些約束:

  • 必須提供一個名為 size 的 member function,並傳回一個整數值。

  • 必須支援一個 operator!= function,用來比較兩個 T 物件。




  • 不過由於有 operator overloading 帶來的可能性,所以這兩個約束都不需要滿足。

    雖然 T 必須支援 size function,但這個 function 可以從 base class 繼承,而且並不需要回傳一個整數值,甚至不需要回傳一個數值型別,它甚至不需要回傳一個定義有 operator> 的型別。

    它唯一需要做的是傳回一個型別為 X 的物件,而 X 物件加上一個 int(10 的型別) 必須能夠喚起一個 operator>。

    這個 operator> 不需要非得取得一個型別為 X 的參數,也可以取得型別 Y 參數,只有存在一個隱式轉換能夠將型別 X 的物件轉換為型別 Y 的物件。



    同理,T 也不需要支援 operator!=,只要 operator!= 接受一個型別為 X 的物件和一個型別為 Y 的物件,T 可被轉換為 X,而 someNastyWidget 的型別可被轉換為 Y,這樣就可以有效喚起 operator!=。



    以上分析並未考慮這樣的可能性:operator&& 被重載,從一個連接詞改變或許完全不同的某種東西,從而改變上述算式的意義。



    加諸於 template 參數身上的 implicit interface,就像加諸於 class 物件身上的 explicit interface 一樣真實,而且兩者都在編譯期完全檢查,就像無法以一種「與 class 提供之 explicit interface 矛盾」的方式來使用物件,也無法在 template 中使用「不支援 template 所要求之 implicit interface」的物件,兩者都會造成無法通過 compile。




    classes 和 templates 都支援 interfaces 和 polymorphism。

    對 class 而言,interfaces 為 explicit,以 function signatures 為中心, polymorphism 則是透過 virtual functions 發生於 run-time。

    對 template 而言,interfaces 為 implicit,奠基於 valid expressions, polymorphism 則是透過 template instantiation 和 function overloading resolution 發生於 compilation。





    42、Understand the two meanings of typename.

    以下 template 宣告式中,class 和 typename 有何不同:

    template&ltclass T&gt class Widget;// 使用 "class"
    template&lttypename T&gt class Widget;// 使用 "type name"



    答案是:沒有不同,在宣告 template 型別參數時,從 C++ 的角度來看,不論使用關鍵字 class 或 typename,意義完全相同。



    然而 C++ 並不總是把 class 和 typename 視為等價,有時候一定得使用 typename,為了解其時機,先談談在 template 內所指涉的兩種名稱:

    template&lttypename C&gt
    void print2nd(const C& container) { // 列印容器內的第二個元素
    if (container.size() >= 2) { // 注意,這不是有效的程式碼
    C::const_iterator iter(container.begin());
    ++iter;
    int value = *iter;
    std::out << value;
    }
    }



    程式碼中,iter 的型別是 C::const_iterator,實際是什麼必須取決於 template 參數 C;在 template 內出現的名稱如果相依於某個 template 參數,稱之為從屬名稱(dependent name);如果從屬名稱在 class 內呈巢狀,稱為巢狀從屬名稱(nested dependent name)。

    C::const_iterator 就是這樣一個名稱,事實上它還是個巢狀從屬型別名稱(nested dependent type name),也就是個巢狀從屬名稱並且指涉某型別。



    print2nd() 內的另一個 local 變數 value,型別為 int,並不依賴任何 template 參數內的名稱,這樣的名稱稱為非從屬名稱(non-independent name)。



    nested dependent name 有可能導致 parsing 困難,修改 print2nd():

    void print2nd(const C& container) {
    C::const_iterator* x;
    ...
    }



    看起來像是宣告 x 為一個 local variable,它是個指標,指向一個 C::const_iterator,但,之所以會這麼認為,是因為已經認定 C::const_iterator 是個型別。

    如果 C::const_iterator 不是個型別呢?如果 C 有個 static 成員變數碰巧命名為 const_iterator,或如果 x 碰巧是個 global variable ?這樣的話,上述的程式碼就會變成一個相乘動作:C::const_iterator 乘以 x 。



    在知道 C 是什麼之前,沒有辦法可以知道 C::const_iterator 是否為一個型別,而當 compiler 開始解析 template print2nd 時,尚未確知 C 是什麼東西,在 C++ 中有個規則可以決議此一歧義狀態:

    如果解析器在 template 中遭遇一個 nested dependent name,將預設這個名稱不是個型別,除非明確告訴它是,方法是在緊臨這個名稱之前放置關鍵字 typename 即可:

    template&lttypename C&gt
    void print2nd(const C& container) { // 列印容器內的第二個元素
    if (container.size() >= 2) {
    typename C::const_iterator iter(container.begin());
    ++iter;
    int value = *iter;
    std::out << value;
    }
    }



    typename 只被用來驗明 nested dependent type name,其它名稱不該有它的存在:

    template&lttypename C&gt // 允許使用 "typename" 或 "class"
    void f(const C& container, // 不允許使用 "typename"
    typename C::iterator iter); // 一定要使用 "typename"



    「typename 必須做為 nested dependent type name 的前導詞」這一規則的例外是,typename 不可以出現在 base class list 內的 nested dependent type name 之前,也不可以在 member initialization list 中做為 base class 飾詞:

    template&lttypename T&gt
    class Derived : public Base::Nested { // 不允許 "typename"
    public:
    explicit Derived(int x)
    : Base::Nested(x) { // 不允許 "typename"
    typename Base::Nested temp; // 需要加上 "typename"
    ...
    }
    ...
    };




    最後一個 typename 的例子,這也是可以會在真實程式中所看到的代表性例子,假設正在撰寫一個 function template,它接受一個 iterator,在 function 中,打算為該 iterator 指涉的物件做一份 local 複件 temp:

    template&lttypename IterT&gt
    void workWithIterator(IterT iter) {
    typename std::iterator_traits::value_type temp(*iter);
    ...
    }



    typename std::iterator_traits::value_type 只不過是標準 trait class 的一種運用,詳見條款 47,相當於說「型別為 IterT 之物件所指之物的型別」,這個述句宣告一個 local variable - temp,使用 IterT 物件所指物的相同型別,並將 temp 初始化為 iter 所指物。

    也就是說如果 IterT 是 vector&ltint&gt::iterator,temp 的型別就是 int,如果 IterT 是 vector&ltstring&gt::iterator,temp 的型別就是 string。



    加上 typedef 的應用是:

    template&lttypename IterT&gt
    void workWithIterator(IterT iter) {
    typedef typename std::iterator_traits::value_type value_type;
    value_type temp(*iter);
    ...
    }




    宣告 template 參數時,前綴關鍵字可以是 class 也可以是 typename。

    nested dependent type names 必須使用關鍵字 typename 來標識 ,除非是在 base class lists 或 member initialization list 內。






    43、Know how to access names in templatized base classes.

    以下程式,能夠傳送訊息到不同的公司,訊息要嘛是明碼,要嘛是加密過的,如果在編譯期間,有足夠資訊來決定哪一個訊息傳至哪一家公司,就可以採用奠基於 template 之上的解法:

    class CompanyA {
    public:
    ...
    void sendCleartext(const std::string& msg);
    void sendEncrypted(const std::string& msg);
    ...
    };

    class CompanyB {
    public:
    ...
    void sendCleartext(const std::string& msg);
    void sendEncrypted(const std::string& msg);
    ...
    };

    class MsgInfo { ... }; // 用來保存資訊,以產生訊息

    template
    class MsgSender {
    public:
    ...
    void sendClear(const MsgInfo& info) {
    std::string msg;
    ... // 根據 info,產生 msg
    Company c;
    c.sendCleartext(msg);
    }
    void sendSecret(const MsgInfo& info) {
    std::string msg;
    Company c;
    c.sendEncrypted(msg);
    }
    };

    template
    class LoggingMsgSender : public MsgSender {
    public:
    ...
    void sendClearMsg(const MsgInfo& info) {
    ... // 紀錄傳送前的 log
    sendClear(info);
    // 呼叫 base class function,尚無法通過 compile
    ... // 紀錄傳送後的 log
    }
    ...
    };



    上述程式碼無法通過編譯的原因在於,當 compiler 遭遇 class template LoggingMsgSender 定義式時,並不知道是繼承什麼樣的 class,當然它繼承的是 MsgSender,但其中的 Company 是個 template 參數,不到 LoggingMsgSender 被具現化後,無法確切得知 Company 是什麼,更簡單的說,就是沒辦法知道 Company 是否有個 sendClear function。



    為了讓問題更具體化,假設有個 class CompanyZ 堅持使用加密通訊:


    class CompanyZ { // 不提供 sendCleartext function
    public:
    ...
    void sendEncrypted(const std::string& msg);
    ...
    };



    一般性的 MsgSender template 對 companyZ 並不適合,因為 CompanyZ 並不提供 sendCleartext function,所以必須針對 CompanyZ 產生一個 MsgSender specialization:

    template&lt&gt // MsgSender total template specialization
    class MsgSender&ltCompanyZ&gt {
    public:
    ...
    void sendSecret(const MsgInfo& info) {
    ...
    }
    };



    class 定義式最前頭的 "template&lt&gt" 語法象徵這既不是 template,也不是標準 class,而是個 specialized 的 MsgSender template,在 template 引數是 CompanyZ 時被使用。

    這是所謂的 total template specialization:

    template MsgSender 針對型別 CompanyZ 特化了,而且其特化是全面性的,也就是說一旦型別參數被定義為 CompanyZ,再沒有其它 template 可供變化。



    如此,考慮到 MsgSender 針對 CompanyZ 進行了 total template specialization,則在 derived class - LoggingMsgSender 中,將不存在 sendClear(info) 這個 function,而這也是這段程式碼無法通過 compile 的原因。



    就某種意義而言,當從 Object Oriented C++ 跨進 Template C++,inheritance 就不像以前那般暢行無阻了。




    有三種方式,可以將上述無法通過 compile 的程式作修正,第一是在 base class function 呼叫動作之前加上 "this->":

    template
    class LoggingMsgSender : public MsgSender {
    public:
    ...
    void sendClearMsg(const MsgInfo& info) {
    ... // 紀錄傳送前的 log
    this->sendClear(info); // 假設 sendClear 將被繼承
    ... // 紀錄傳送後的 log
    }
    ...
    };



    第二是使用 using 宣告式,詳見條款 33

    template
    class LoggingMsgSender : public MsgSender {
    public:
    ...
    void sendClearMsg(const MsgInfo& info) {
    using MsgSender::sendClear;
    ... // 紀錄傳送前的 log
    sendClear(info); // 假設 sendClear 位於 base class 內
    ... // 紀錄傳送後的 log
    }
    ...
    };



    第三種作法是明白指出被呼叫的 function 位於 base class 內:

    template
    class LoggingMsgSender : public MsgSender {
    public:
    ...
    void sendClearMsg(const MsgInfo& info) {
    ... // 紀錄傳送前的 log
    MsgSender::sendClear(info); // 假設 sendClear 將被繼承
    ... // 紀錄傳送後的 log
    }
    ...
    };



    但第三種,往往是最不讓人滿意的,因為如果被呼叫的是 virtual function,上述的 explicit qualification 會關閉「virtual 綁定行為」。



    從名稱 visibility point 的角度出發,上述每個解法所做的事情都相同:

    對 compiler 承諾「base class template 的任何特化版本都將支援其一般版本所提供的介面」,但如果這個承諾最終未被實踐出來,往後的 compile 還是會無法通過:

    LoggintMsgSender zMsgSender;
    MsgInfo msgData;
    ...
    zMsgSender.sendClearMsg(msgData); // 無法通過 compile



    因為此時 compiler 知道 base class 是個 template 特化版本 MsgSender,而且還知道這個 class 並不提供 sendClear function,但卻被 sendClearMsg() 嘗試呼叫。




    可在 derived class template 內透過 "this->" 指涉 base class template 內的 member name,或利用 using declaration,又或藉由 explicit base class qualification,來達到使用 base classes 內的 member name。





    44、Factor parameter-independent code out of templates.

    template 是節省時間和避免程式碼重複的一個妙方,不再需要鍵入 20 個類似的 class 而每個帶有 15 個 member function,只需要鍵入一個 class template,接著留給 compiler 去具現化那 20 個需要的相關 class 及 300 個 function。

    (class template 的 member function 只有在被使用時才被具現化。)

    function template 也有類似的訴求,取代寫取多 function,只需要寫一個 function template,然後讓 compiler 做剩餘的事情。



    不過,如果不小心,使用 template 將可能會導致程式碼膨脹(code bloat):

    其二進制碼帶著重複的程式碼、資料。

    這樣的結果有可能 source code 看起來整齊,但 object code 卻不是這麼回事。



    舉個例子,為固定尺寸的正方矩陣編寫一個 template,該矩陣的性質之一是支援反矩陣運算:

    template&lttypename T, std::size_t n&gt
    class SquareMatrix {
    public:
    ...
    void invert(); // 逆反矩陣
    };

    SquareMatrix sm1;
    ...
    sm1.invert(); // 呼叫 SquareMatrix::invert

    SquareMatrix sm2;
    ...
    sm2.invert(); // 呼叫 SquareMatrix::invert



    這個 template 接受一個型別參數 T,還接受一個型別為 size_t 的參數,這是個非型別參數(non-type parameter)。

    而在接下來的程式碼中,會造成具現化兩份 invert,這些 function 並非完全相同,因為其中一個操作的是 5x5 矩陣,另一個是 10x10 矩陣,但除了常數 5 和 10 之外,兩個 function 的其它部分完全相同,這是 template 引出程式碼膨脹的一個典型例子。



    如果看到兩個 function 完全相同,除了一個使用 5,另一個使用 10,本能地會為它們建立一個帶數值參數的 function:

    template&lttypename T&gt
    class SquareMatrixBase {
    protected:
    ...
    void invert(std::size_t matrixSize); // 逆反矩陣
    };

    template&lttypename T, std::size_t n&gt
    class SquareMatrix : private SquareMatrixBase&ltT&gt {
    private:
    using SquareMatrixBase::invert; // 避免遮掩 base 版的 invert
    public:
    ...
    void invert(s) { this->invert(n) };
    // 避免遮掩 templatized base class 內的 function
    };



    帶參數的 invert() 位於 base class SquareMatrixBase 中,而 SquareMatrixBase 也是個 template,但只對「矩陣元素物件的型別」參數化。



    還有個棘手的問題,SquareMatrixBase::invert() 如何知道該操作什麼資料?

    雖然從參數中可以知道矩陣尺寸,但如何得知哪個特定矩陣的資料在哪?

    一個可能的作法是為 SquareMatrixBase::invert() 添加參數,也許是個 pointer,指向一塊用來放置矩陣資料的記憶體起始點。

    又或是令 SquareMatrixBase 貯存一個 pointer,指向矩陣數值所在的記憶體:

    template&lttypename T&gt
    class SquareMatrixBase {
    protected:
    SquareMatrixBase(std::size_t n, T* pMem)
    : size(n)
    , pData(pMem) {
    }
    void setDataPtr(T* ptr) { pData = ptr; }
    ...
    private:
    std::size_t size; // 矩陣大小
    T* pData; // pointer,指向矩陣內容
    };

    template&lttypename T, std::size_t n&gt
    class SquareMatrix : private SquareMatrixBase&ltT&gt {
    public:
    SquareMatrix()
    : SquareMatrixBase(n, data)
    , pData(new T[n*n]) {
    this->setDataPtr(pData.get());
    }
    ...
    private:
    boost::scoped_array pData; // 祥見條款 13
    };




    Template 會生成多個 classes 和 functions,所以任何 template 程式碼都不該與某個造成膨脹的 template 參數產生相依關係。

    因非型別模板參數(non-type template parameters) 而造成的程式膨脹,往往可消除,作法是以 function parameters 或 class data members 取代 template parameters。

    因型別參數(type parameters) 而造成的程式膨脹,往往可降低,作法是讓帶有完全相同二進制表述 (identical binary representation) 的具現型別 (instantiation type) 共享實作碼






    45、Use member function templates to accept "all compatible types."

    所謂 smart pointer 是「行為像指標」的物件,如條款 13 提及 std::auto_ptr 及 tr1::shared_ptr 如何能被用來在正確時機自動刪除 heap-based 資源。



    不過真實指標做得很好的一件事是,支援 implicit conversion,derived class pointer 可以隱式轉換為 base class pointer,「指向 non-const 物件」的 pointer 可以轉換為「指向 const 物件」… 等等:

    class Top { ... };
    class Middle : public Top { ... };
    class Bottom : public Middle { ... };

    Top* pt1 = new Middle; // 將 Middle* 轉換為 Top*
    Top* pt2 = new Bottom; // 將 Bottom* 轉換為 Top*
    const Top* pct2 = pt1; // 將 Top* 轉換為 const Top*



    如果要在使用者定義的 smart pointer 中模擬上述轉換,則稍稍有點麻煩:

    template&lttypename T&gt
    class SmartPtr {
    public: // smart pointer 通常以
    explicit SmartPtr(T* realPtr); // 內建原始 pointer 完成初始化
    ...
    };

    SmartPtr&ltTop&gt pt1 = SmartPtr&ltMiddle&gt(new Middle);
    SmartPtr&ltTop&gt pt2 = SmartPtr&ltBottom&gt(new Bottom);
    SmartPtr&ltconst Top&gt pct2 = pt1;



    同一個 template 的不同具現體(instantiation) 之間並不存在什麼與生俱來的固有關係,所以 compiler 將視 SmartPtr&ltMiddle&gt 和 SmartPtr&ltBottom&gt 為完全不同的 class,而為了獲得 SmartPtr class 之間的轉換能力,則必須將它們明確地編寫出來。




    template 和 generic programming

    在上個例子中,每個述句產生了一個新式 smart pointer 物件,所以要關注的是如何編寫 smart pointer 的 constructor,使其行為能夠滿足轉型需要。

    一個很關鍵的觀察結果是:可能永遠無法寫出所需要的所有 constructor,因為這個繼承體系在未來可能會有所擴充。



    就原理而言,此例中所需要的 constructor 數量沒有止盡,因為一個 template 可被無限量具現化,因此,似乎此時需要的不是為 SmartPtr 寫一個 constructor function,而是為它寫一個 constructor template,這樣的 template 是所謂 member function template,簡稱為 member template,其作用是為 class 生成 function:

    template&lttypename T&gt
    class SmartPtr {
    public:
    template&lttypename U&gt // member template
    SmartPtr (const SmartPtr&ltU&gt& other); // 為了生成 copy constructor
    ...
    };



    上述例子是,對任何型別 T 和任何型別 U,可以根據 SmartPtr&ltU&gt 生成一個 SmartPtr&ltT&gt。

    這一類 constructor 根據物件 u 產生物件 t,而 u 和 t 的型別是同一個 template 的不同具現體,有時也被稱為 generalized copy constructor。



    此例中的 copy constructor 並未被宣告為 explicit,因為在原始 pointer 型別之間的轉換是隱式轉換,無需明白寫出轉型動作(cast)。



    不過上述這個為 SmartPtr 而寫的 generalized copy constructor 提供的東西比需要的還多,因為除了可以根據一個 SmartPtr&ltBottom&gt 生成一個 SmartPtr&ltTop&gt,但也造成可以根據一個 SmartPtr&ltTop&gt 生成一個 SmartPtr&ltBottom&gt,這對 public inheritance 而言是矛盾的,詳見條款 32



    假設 SmartPtr 遵循 auto_ptr 和 tr1::shared_ptr 所提供的榜樣,也提供一個 get member function,傳回 smart pointer 所持有的那個原始 pointer 的副本,則可以在「建構模板」實作碼中約束轉換行為:

    template&lttypename T&gt
    class SmartPtr {
    public:
    template&lttypename U&gt
    SmartPtr (const SmartPtr&ltU&gt& other) // 以 other 的 heldPtr
    : heldPtr(other.get()) { ... } // 初始化 this 的 heldPtr
    T* get() const { return heldPtr; }
    ...
    private:
    T* heldPtr; // 這個 SmartPtr 持有的內建原始 pointer
    };



    使用 member initialization list 來初始化 SmartPtr&ltT&gt 之內型別為 T* 的 member variable,並以型別為 U* 的 pointer 作為初值,這個行為只有當「存在某個隱式轉換可將一個 U* pointer 轉換為一個 T* pointer」時才能通過 compile,這正是此例子中所需要的。

    最終效益是 SmartPtr&ltT&gt 現在有了一個泛化 copy constructor,這個 constructor 只在其所獲得的引數隸適當(相容)型別時,才會通過 compile。



    member function template 的效用不限於 constructor,它們常扮演的另一個角色是支援賦值操作。




    member function template 並不改變語言規則,而語言規則說,如果程式需要一個 copy constructor,如果沒有宣告它,則 compiler 會暗自生成一個,而在 class 內宣告 generalized copy constructor 並不會阻止 compiler 生成它們自己的 copy constructor,相同的規則也適用於 copy assignment operator,下面這個例子正是 tr1::shared_ptr 的定義摘要:

    template&ltclass T&gt
    class shared_ptr {
    public:
    shared_ptr(shared_ptr const& r); // copy constructor
    template&ltclass Y&gt // generalized copy constructor
    shared_ptr(shared_ptr&ltY&gt const& r);

    shared_ptr& operator=(shared_ptr const& r); // copy assignment
    template&ltclass Y&gt // generalized copy assignment
    shared_ptr& operator=(shared_ptr&ltY&gt const& r);
    ...
    };




    請使用 member function template 生成「可接受所有相同型別」的 function。

    如果宣告 member template 用於「generalized copy constructor」或「generalized assignment operator」,還是得需要宣告正常的 copy constructor 及 copy assignment operator。






    46、Define non-member functions inside templates when type conversions are desired.

    條款 24 討論過為什麼唯有 non-member function 才有能力「在所有引數身上實施隱式型別轉換」,該條款以 Rational class 的 operator* function 為例,本討論將 Rational 和 operator* 模板化了:

    template&lttypename T&gt
    class Rational {
    public:
    Rational(const T& numerator = 0, const T& denominator = 1);
    const T numerator() const;
    const T denominator() const;
    ...
    };

    template&lttypename T&gt
    const Rational&ltT&gt operator* (const Rational&ltT&gt& lhs,
    const Rational&ltT&gt& rhs) {
    ...
    }

    Rational&ltint&gt oneHalf(1, 2);
    Rational&ltint&gt result = oneHalf * 2; // 錯誤,無法通過 compile



    如果條款 24一樣,也希望可以支援 mixed-mode 算術運算,但從上例的 compile error 來看,模板化的 Rational 內的某些東西似乎和其 non-template 版本不同。

    條款 24內,compiler 知道嘗試呼叫的 function 是接受兩個 Rational 參數的那個 operator*,但在這個例子,compiler 不知道哪個 function 該被呼叫。

    此例中,compiler 試圖想出什麼 function 被名為 operator* 的 template 具現化出來,compiler 知道它可以具現化某個「名為 operator* 並接受兩個 Ration&ltT&gt 參數」的 function,但要完成這一具現化行為,必須先算出 T 是什麼。



    為了推導 T,compiler 看了 operator* 呼叫動作用的引數型別,分別是 Rational&ltint&gt 和 int。

    以 operator* 的第一參數被宣告為 Rational&ltT&gt,而傳遞給 operator* 的第一引數的型別就是 Rational&ltint&gt,所以 T 一定是 int。

    但 operator* 的第二參數型別是 int,而 compiler 在 template 引數推導過程中從不將隱式型別轉換納入考慮,所以不會將 int 轉換為 Rational&ltint&gt。



    雖然這樣的轉換在 function call 過程中被使用,但在能夠呼叫一個 function 之前,首先必須知道那個 function 的存在,而為了知道它,必須先為相關的 function template 推導出參數型別,但 template 引數推導過程中,並不考慮採納「藉由 constructor 而發生的」隱式型別轉換。



    只要利用一個事實,就可以緩和 compiler 在 template 引數推導方面到的挑戰:

    template class 內的 friend 宣告式可以指涉某個特定 function。

    這意味著 class Rational&ltT&gt 可以宣告 operator* 是它的一個 friend function,class template 並不倚賴 template 引數推導,所以 compiler 總是能夠在 class Rational&ltT&gt 具現化時得知 T:

    template&lttypename T&gt
    class Rational {
    public:
    ...
    friend const Rational operator*(const Rational& lhs,
    const Rational& rhs);
    // 在 class template 內,template 名稱可被用來做為
    //「template 和其參數」的簡略表達方式

    };

    template&lttypename T&gt
    const Rational&ltT&gt operator* (const Rational&ltT&gt& lhs,
    const Rational&ltT&gt& rhs) {
    ...
    }



    現在對 operator* 的 mixed mode 呼叫可以通過 compile,因為當物件 oneHalf 被宣告為一個 Rational&ltint&gt,class Rational&ltint&gt 於是被具現化出來,而作為過程的一部分,所以 friend function operator* 也就被自動產生出來,而這個 function 並非 function template,所以 compiler 可在呼叫它時使用隱式轉換,而這就是 mixed mode 呼叫之所以成功的原因。



    但目前為止,雖然可以通過 compile,因為 compiler 知道要呼叫哪一個 function,卻無法 link,因為那個 function 只被宣告於 Rational 內,並沒有被定義出來。



    或許最簡單的的可行辦法是將 operator* function 本體合併至其宣告式內:

    template&lttypename T&gt
    class Rational {
    public:
    ...
    friend const Rational operator*(const Rational& lhs,
    const Rational& rhs) {
    return Rational(lhs.numerator() * rhs.umerator() ,
    lhs.denominator() * rhs.denominator());
    }
    };



    這個技術中,雖然使用了 friend,卻與 friend 的傳統用途「存取 class 的 non-public 成分」毫不相干。

    為了讓型別轉換可能發生在所有引數身上:需要一個 non-member function;為了令這個 function 被自動具現化:需要將它宣告在 class 內部;而在 class 內宣告 non-member function 的唯一辦法就是:令它成為一個 friend 。



    如同條款 30 所說,定義在 class 內的 function 都暗自成為 inline,為了將 inline 宣告所帶來的衝擊最小化,作法是令 operator* 不做任保事,只呼叫一個定義於 class 外部的輔助 function。

    但在上例中,這樣做,並沒有太大意義,因為 operator* 已經是個單行 function。



    「Rational 是個 template」所以上述的輔助 function 通常也是個 template:

    template&lttypename T&gt class Rational; // 宣告 Rational template
    template&lttypename T&gt // 宣告 helper template
    class Rational&ltT&gt doMultiply(const Rational& lhs,
    const Rational& rhs);

    template&lttypename T&gt
    class Rational {
    public:
    ...
    friend const Rational operator*(const Rational& lhs,
    const Rational& rhs) {
    return doMultiply(lhs, rhs); // 令 friend 呼叫 helper
    }
    };

    template&lttypename T&gt
    const Rational&ltT&gt doMultiply(const Rational& lhs,
    const Rational& rhs) {
    return Rational(lhs.numerator() * rhs.umerator() ,
    lhs.denominator() * rhs.denominator());
    }



    做為一個 template,doMultiply 當然不支援 mixed mode 乘法,但它其實不需要,它只被 operator* 呼叫,而 operator* 支援了 mixed mode 操作。




    當在編寫一個 class template,而它所提供之「將此 template 相關的」function 支援「所有參數之隱式型別轉換」時,將那些 function 定義為「class template 內部的 friend function」。





    47、Use traits classes for information about types.

    STL 主要由「用以表現容器、iterator 和演算法」的 template 構成,但也函蓋若干工具性 template,其中之一是 advance,用來將 iterator 移動某個距離:

    template&lttypename IterT, typename DistT&gt
    void advance(IterT& iter, DistT d);
    // 將 iterator 向前移動 d 單位,如果 d < 0 則向後移動



    觀念上 advance 只是做 iter + = d 動作,但其實不可以全然這麼實踐,因為只有 random access iterator 才支援 += 操作,其它威力不那麼強大的 iterator,advance 須反覆執行 ++ 或 --,共 d 次。



    STL 的 iterator 分成 5 個 categories:

    • input iterator:

    • output iterator:

    • forward iterator:

    • bidirectional iterator:

    • random access iterator:




    對於這 5 種分類,C++ 標準程式庫分別提供專屬的 tag struct 加以確認:

    struct input_iterator_tag {};
    struct output_iterator_tag {};
    struct forward_iterator_tag : public input_iterator_tag {};
    struct bidirectional_iterator_tag : public forward_iterator_tag {};
    struct random_access_iterator_tag : public bidirectional_iterator_tag {};



    所以在實作 advance 時,希望能以下列方式實作:

    template&lttypename IterT, typename DistT&gt
    void advance(IterT& iter, DistT d) {
    if (iter is a random access iterator) {
    iter += d; // 針對 random access iterator 使用算術運算
    } else {
    if (d >= 0) { while (d--) ++iter; } // 其它 iterator
    else { while (d++) --iter; } // 則反覆呼叫 ++ 或 --
    }
    }



    這種作法首先必須判斷 iter 是否為 random access iterator,也就是需要取得型別的某些資訊,這也是 traits 存在的理由:

    traits 的存在,將使得可以在 compile 期間取得某些型別資訊。



    traits 並不是 C++ keyword,也不是一個預先定義好的構件,它們是一種技術,也是 C++ programmer 所共同遵守的協定。

    這個技術的要求之一是,它對 build-in 型別和 user-defined 型別的表現必須一樣好。



    traits 的標準技術是把型別資訊放進一個 template 及其一或多個特化版本中,這樣的 template 在標準函式庫有若干個,其中針對 iterator 被命名為 iterator_traits:

    template&lttypename IterT&gt // template,用來處理
    struct iterator_traits; // iterator 的相關資訊



    iteractor_traits 是個 struct,習慣上 traits 總是被實作為 structs,但卻又往往被稱為 struct classes。



    iterator_traits 的運作方式是:

    針對每一個型別 IterT,在 struct iterator_traits&ltIterT&gt 內一定宣告某個 typedef 名為 iterator_category,這個 typedef 用來確認 IterT 的 iterator category。



    iterator_traits 以兩個部分實現上述所言,首先它要求每一個「user-defined iterator type」必須嵌套一個 typedef,名為 iterator-category,用來確認適當的 tag struct:

    template &lt ... &gt // 略而未寫 template 參數
    class deque {
    public:
    class iterator {
    typedef random_access_iterator_tag iterator_category;
    };
    ...
    };

    template &lt ... &gt
    class list {
    public:
    class iterator {
    typedef bidirectional_iterator_tag iterator_category;
    };
    ...
    };

    template &lttypename IterT&gt
    struct iterator_traits {
    typedef typename IterT::iterator_category iterator_category;
    };



    但上述要求對 user-defined type 行得通,但對 pointer (也是一種 iterator) 行不通,因為 pointer 不可能存在嵌套式 typedef,所以 iterator_traits 的第二部分如下,專門用來對付 pointer。



    為了支援 pointer iterator,iterator_traits 特別針對 pointer type 提供一個 partial template specialization,由於 pointer 的行徑與 random access iterator 類似,所以 iterator_traits 為 pointer 指定的 iterator type 是:

    template &lttypename IterT&gt // partial template specialization
    struct iterator_traits&ltIterT*&gt { // 針對 built-in pointer
    typedef random_access_iterator_tag iterator_category;
    };



    總結,要如何設計並實作一個 traits class:

    • 確認若干希望將來可取得的型別相關資訊,以 iterator 為例,希望可以取得其 category 資訊。

    • 為該資訊選擇一個名稱,如:iterator_category

    • 提供一個 template 和一組 partial template specialization,如上述的 iterator_traits,內含希望支援的型別相關資訊。




    有了 iterator_traits 後(實際上是 std::iterator_traits,因為它是 C++ 標準函式庫的一部分),可以對 advance 實踐先前的 pseudocode:

    template&lttypename IterT, typename DistT&gt
    void advance(IterT& iter, DistT d) {
    if (typeid(typename std::iterator_traits&ltIterT&gt::iterator_category)
    == typeid(std::random_access_iterator_tag)) {
    ...
    }



    但這樣子的作法並非正確,首先它會導致 compile 問題,這在條款 48 才探討。

    此刻要根本考慮的問題是 IterT 型別在 compile 期間獲知,所以 iterator_traits&ltIterT&gt::iterator_category 也可以 compile 期間確定,但 if 述句卻是在執行期才會核定。



    有沒有一個條件式判斷「compile 期核定成功」之型別?

    恰巧 C++ 有一個取得這種行為的辦法,也就是 overloading。



    所需要做的,就是讓 advance 產生兩版重載 function,內含 advance 的本質內容,但接受不同類型的 iterator_category 物件:

    template&lttypename IterT, typename DistT&gt
    void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag) {
    iter += d;
    }

    template&lttypename IterT, typename DistT&gt
    void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag) {
    if (d >= 0) { while (d--) ++iter; }
    else { while (d++) --iter; }
    }

    template&lttypename IterT, typename DistT&gt
    void doAdvance(IterT& iter, DistT d, std::input_iterator_tag) {
    if (d < 0) {
    throw std::out_of_range("Negative distance");
    }
    while (d--) ++iter;
    }
    //



    有了這些 doAdvance overloading 版本,advance 需要做的只是呼叫它們並額外傳遞一個帶有適當 iterator type 的物件,於是 compiler 會運用 overloading resoluation 來呼叫適當的實作碼:

    template&lttypename IterT, typename DistT&gt
    void advance(IterT& iter, DistT d) {
    doAdvance(iter, d,
    typename std::iterator_traits&ltIterT&gt::iterator_cagetory()
    );
    }



    總結如何使用一個 traits class:

    • 建立一組 overloading function(身份像勞工) 或 function template(如 doAdvance),彼此間的差異只在於各自的 traits 參數,令每個 function 實作碼與其接受之 traits 資訊相應。

    • 建立一個控制 function(身份像工頭),或 function template(如 advance),用來呼叫上述的那些「勞工 function」,並傳遞 traits class 所提供的資訊。




    traits 廣泛用於標準函式庫,除了包含上述討論的 iterator_traits,還有另外四份 iterator 相關資訊,其中最有用的是 value_type,詳見條款42



    TR1 ,見條款52,導入許多新的 traits classes 用以提供型別資訊,包括 is_fundamental&ltT&gt,is_array&ltT&gt 以及 is_base_of&ltT1, T2&gt,總計 TR1 為標準 C++ 添加了 50 個以上的 traits classes。




    traits classes 使得「型別相關資訊」在 compile 期可用,以 template 和 partial template specialization 完成實作。

    整合 overloading 後,traits classes 有可能在 compile 期間,就對型別執行 if ... else 測試。






    48、Be ware of template metaprogramming.

    template metaprogramming(TMP, 模板超編程) 是編寫 template-based C++ 程式並執行於 compile 期的過程。

    而所謂 template metaprogram 是以 C++ 寫成、執行於 C++ 編譯器內的程式,一旦 TMP 程式結束執行,其輸出,也就是從 template 具現出來的若干 C++ source code,便會一如往常地被編譯。



    TMP 有兩個偉大的效力:

    • 它讓某些事情更容易,如果沒有它,那些事情是困難的,甚至不可能的。

    • 由於 template metaprograms 執行於 compile-time,因此可將工作從 run-time 轉到 compile-time,這導致兩個結果:

      • 某些錯誤原本通常在 run-time 才能偵測到,現在可在 compile-time 找出來。

      • 使用 TMP 的 C++ 程式可能在每一方面都更高效:較小的可執行檔、較短的執行期、較少的記憶體需求;然而將工作從 run-time 移到 compile-time 也會造成編譯時間變長。





    考慮條款47 中導入的 STL advance pseudo code ,可以使用 typeid 讓其中的 pseudo code 成真,但條款47 指出,這個 typeid-based 解法的效率比 traits 解法低,因為:

    • 型別測試發生於 run-time 而非 compile-time。

    • 「run-time 型別測試」程式碼會被連結於可執行檔中。




    條款47 曾經提過 advance 的 typeid-based 實作方式可能導致 compile-time 問題,例子如下:

    std::list&ltint&gt::iterator iter;
    ...
    advance(iter, 10); // 無法通過 compile



    將 template 參數 IterT 和 DistT 分別替換為 iter 和 10 的型別之後,可以得到:

    void advance(std::list&ltint&gt::iterator& iter, int d) {
    if (typeid(std::iterator_traits<:list>::iterator_category
    == typeid(std::random_access_iterator_tag)) {
    iter += d; // 錯誤
    } else {
    if (d >= 0) { while (d--) ++iter; }
    else { while (d++) --iter; }
    }
    }



    問題就出在那行使用了 += 運算子的程式碼,那便是嘗試在一個 list&ltint&gt::iterator身上使用 +=,因為 compiler 必須確保所有的 source code 都有效,縱使是不會執行起來的 source code。



    TMP 已被證明是個「turing-complete machine」,意思是它的威力大到足以計算任何事物。

    使用 TMP 可以宣告變數、執行迴圈、編寫及呼叫 function…,但這般構件相對於「正常的」C++ 對應物看起來很是不同,如條款 47 展示的 TMP if ... else 條件句是藉由 templates 和其特化體表現出來。




    TMP 並沒有真正的迴圈構件,所 以迴圈效果係藉由 recursion 完成,而 TMP 的 recursion 甚至不是正常種類,因為 TMP 迴圈並不涉及 recursiive function 呼叫,而是涉及「recursive template instantiation」。



    TMP 的起手程式是在 compile-time 計算 factorial,而 factorial 運算將示範如何透過「recursive template instantiation」實現迴圈,以及如何在 TMP 中產生和使用變數:

    template&ltunsigned n&gt
    struct Factorial {
    enum { value = n * Factorial::value };
    };

    template&lt&gt
    struct Factorial<0> {
    enum { value = 1 };
    };

    int main() {
    std::cout << Factorial<5>::value << std::endl; // 120
    std::cout << Factorial<10>::value << std::endl; // 3628800
    return 0;
    }



    迴圈發生在 template instantiate Factorial&ltn&gt 內部指涉另一個 template instantiate Factorial&ltn-1&gt 之時,和所有良好 recursion 一樣,需要一個特殊情況來造成 recursion 結束:

    template specialization Factorial&lt0&gt。



    每個 factorial template instantiation 都是一個 struct,每個 struct 都使用 enum hact (見條款2) 宣告一個名為 value 的 TMP 變數,value 用來保存當前計算所得的階乘值,如果 TMP 擁有真正的 recursion 構件, value 應該在每次 recursion 內獲得更新,但由於 TMP 係以「recursive template instantiation」取代迴圈,所以每個 instantiation 都有自己的一份 value,而每個 value 都有其迴圈內的適當值。



    為求領悟 TMP 之所以值得學習,很重要的一點是先對它能夠達成什麼目標有一個比較好的理解,以下舉出三個例子:

    • 確保量度單位正確:

    • 優化矩陣運算:

    • 可以生成客戶訂製之設計範式(custom design pattern) 實作品:





    template metaprogramming(TMP, 模板超編程) 可將工作由 run-time 移往 compile-time,因而得以實現早期錯誤偵測和更高的執行效率。

    TMP 可被用來生成「植基於政策選擇組合」(based on combinations of policy choices) 的客戶訂製碼,也可用來避免生成對某些特殊型別並不適合的碼。


    silverfoxkkk 發表在 痞客邦 留言(0) 人氣()



    Static vs. Shared vs. Dynamic Loaded Library



    What is a Static Library?

  • The contents of this library are usually machine code files that are not readable by humans.


  • These machine code files are normally generated from code compilation or a similar process.


  • A static library is more flexible than a dynamic library becuase its exact path is irrelevant to the executable that uses it:

    • Static libraries are linked into an executable file and their content has been included in the final program.

    • A dynamic library's content is loaded as the executable runs, so its locations relative to the execuable and in the operating system are important.



  • The contents of static libraries are not linked together because they are not expected to run on their own.


  • Creating a static library that does not link properly within itself is not inherently dangerous as long as the library is linked with any other external files that may be necessary.


  • A library can depend on an external group of object files, or one library anc depend on another.



  • Ref: What is a Static Library



    Program Library HOWTO


    This paper first discusses static libraries, which are installed into a program executable before the program can be run.

    It then discusses shared libraries, which are loaded at program start-up and shared between programs.

    Finally, it discusses dynamically loaded (DL) libraries, which can be loaded and used at any time while a program is running.

    DL libraries aren't really a different kind of library format (both static and shared libraries can be used as DL libraries); instead, the difference is in how DL libraries are used by programmers.












    Ref: Program Library HOWTO



    silverfoxkkk 發表在 痞客邦 留言(0) 人氣()

    1 2 3 4
    Blog Stats
    ⚠️

    成人內容提醒

    本部落格內容僅限年滿十八歲者瀏覽。
    若您未滿十八歲,請立即離開。

    已滿十八歲者,亦請勿將內容提供給未成年人士。