<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    PS,1880后程序員

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

    C++ Primer 之 讀書筆記 第十七章 用于大型程序的工具

     

    第十七章 Tools for Large Programs 用于大型程序的工具

    這一章值得仔細品味。

    17.1 異常處理(Exception Handling)

    C++異常處理包括:

    1.      throw 表達式(throw expressions, throw 引發了異常條件。a throw raises an exceptional condition.

    基本形式:

    // first check that data is for the same item

    if (!item1.same_isbn(item2))

        throw runtime_error("Data must refer to same ISBN");

    2.      try程序塊try blocks,

    基本形式:

    while (cin >> item1 >> item2) {

        try {

            // execute code that will add the two Sales_items

            // if the addition fails, the code throws a runtime_error exception

        } catch (runtime_error err) {

            // remind the user that ISBN must match and prompt for another pair

            cout << err.what()

                 << ""nTry Again? Enter y or n" << endl;

            char c;

            cin >> c;

            if (cin && c == 'n')

                break;     // break out of the while loop

        }

    }

    3.      由標準庫定義的異常類集合。這些異常類用于在throw和相關的catch這件傳遞錯誤信息。A set of exception classes defined by the librarywhich are used to pass the information about an error between a throw and an associated catch.

    17.1.1. 拋出類類型的異常Throwing an Exception of Class

    異常是由拋出對象引起的。對象的類型決定哪一個句柄會被調用。An exception is raised by throwing an object. The type of that object determines which handler will be invoked.

    拋出異常和捕捉異常和實參如何傳遞給函數類似。異常是一個對象,這個對象可以傳遞給非引用的形參,因此異常的了類類型必須是可以拷貝的類類型。Exceptions are thrown and caught in ways that are similar to how arguments are passed to functions. An exception can be an object of any type that can be passed to a nonreference parameter, meaning that it must be possible to copy objects of that type.

    當執行throw的時候,throw后面的語句就不會被執行。控制權由throw轉移到匹配的catchWhen a throw is executed, the statement(s) following the throw are not executed. Instead, control is transferred from the throw to the matching catch.

    throw表達式初始化一個特殊對象,這個對象稱為異常對象(exception object)。例如:runtime_error就是一種異常對象。Instead, the throw expression is used to initialize a special object referred to as the exception object.

    異常與指針Exceptions and Pointers

    拋出指針總是壞主意。拋出指針要求指針所指向的對象在對應處理句柄地方一定是存在的。Throwing a pointer requires that the object to which the pointer points exist wherever the corresponding handler resides.

    17.1.2. 棧展開Stack Unwinding

    為局部對象調用析構函數Destructors Are Called for Local Objects

    棧展開期間,局部對象使用的內存被釋放,類類型的局部對象的析構函數被調用。During stack unwinding, the memory used by local objects is freed and destructors for local objects of class type are run.

    但是在棧展開期間,資源不會被釋放。什么是資源?例如通過調用new動態分配內存。如果程序塊是因為exception退出,編譯器是不會刪除指針的。這樣分配的內存也就不會被釋放。a block might dynamically allocate memory through a call to new. If the block exits due to an exception, the compiler does not delete the pointer. The allocated memory will not be freed.

    析構函數不能拋出異常Destructors Should Never throw Exceptions

    棧展開期間,執行析構函數,異常已經產生,但是還沒有處理。Destructors are often executed during stack unwinding. When destructors are executing, the exception has been raised but not yet handled.

    如果在棧展開期間析構函數也拋出自己的異常,這個異常不會被處理,只會導致調用terminate函數。terminate函數會調用abort函數,強制退出。while stack unwinding is in progress for an exception, a destructor that throws another exception of its own that it does not also handle, causes the library terminate function is called. Ordinarily, terminate calls abort, forcing an abnormal exit from the entire program.

    結論:讓析構函數做任何會引發異常的事情,都是壞主意。it is usually a very bad idea for a destructor to do anything that might cause an exception.

    析構函數和構造函數

    構造函數是可以引發異常的。構造函數引發異常時,對象可能已經部分被構造。即使對象已經被部分構造了,我們也必須保證已經構造的成員能夠被撤銷。Even if the object is only partially constructed, we are guaranteed that the constructed members will be properly destroyed.

    未捕獲的異常終止程序Uncaught Exceptions Terminate the Program

    如果沒有匹配的catch,程序就會調用terminate函數。If no matching catch is found, then the program calls the library terminate function.

    但是這種情況在Java里是不可能發生的,因為編譯的時候就已經翹了。

    17.1.3. 捕獲異常(Catching an Exception

    異常說明符Exception Specifierscatch語句里就像是包含一個形參的形參列表。

    發現匹配的異常處理句柄(Finding a Matching Handler

    拋出異常的類型和catch說明符(exception specifier)必須完全匹配。只允許以下幾種轉換:

    • 從非constconst的轉換。拋出的非const對象異常可以匹配接受const引用的catchConversions from nonconst to const are allowed. That is, a throw of a nonconst object can match a catch specified to take a const reference.
    • 從派生類到基類的轉換。Conversions from derived type to base type are allowed.
    • 數組轉換為指向數組類型的指針;函數轉換為指向函數類型的指針。An array is converted to a pointer to the type of the array; a function is converted to the appropriate pointer to function type.

    異常說明符Exception Specifiers

    異常說明符就是catch語句后面緊跟的形參。它可以是一般的對象,也可以是引用。

    catch(exception &e) { //粉色部分就是異常說明符

        // do cleanup

        // print a message

        cerr << "Exiting: " << e.what() << endl;

        size_t status_indicator = 42; // set and return an

        return(status_indicator);      // error indicator

    }

    異常說明符和繼承

    基類的異常說明符可以用來捕捉其派生類型的對象。an exception specifier for a base class can be used to catch an exception object of a derived type.

    異常說明符的靜態類型決定了catch語句可以執行的操作。the static type of the exception specifier determines the actions that the catch clause may perform.這實際上意味,如果異常對象是派生類,但是catch語句的形參是基類類型,那么異常對象中的派生類部分就不能使用。

    所以,通常,處理繼承相關的異常類型的catch語句應該定義引用類型的形參。Usually, a catch clause that handles an exception of a type related by inheritance ought to define its parameter as a reference.

    catch語句的順序必須反映類型的層次關系

    多個具有繼承關系的catch語句應該是按從繼承層級的最高級到最低級的順序進行排序。也就是越靠后越接近基類。Multiple catch clauses with types related by inheritance must be ordered from most derived type to least derived.總而言之就是要保證派生類的句柄在基類的catch語句的前面。handlers for a derived type occurs before a catch for its base type.

    17.1.4. 重新拋出(Rethrow

    rethrow僅僅就是throw;

    雖然rethrow沒有指定自己的異常,但是一個異常對象依然會沿著調用鏈向上傳遞。拋出的異常是最初的異常對象,而不是catch的形參。當catch的形參是基類類型時,我們不可能知道rethrow拋出的異常的實際類型。這個類型依賴于異常對象的動態類型,而不是靜態類型。Although the rethrow does not specify its own exception, an exception object is still passed up the chain. The exception that is thrown is the original exception object, not the catch parameter. When a catch parameter is a base type, then we cannot know the actual type thrown by a rethrow expression. That type depends on the dynamic type of the exception object, not the static type of the catch parameter.

    一般情況,catch也許會修改它的形參。如果,形參修改后,catch重新拋出異常,那么這些修改只有在異常說明符是引用時,才會繼續傳播。In general, a catch might change its parameter. If, after changing its parameter, the catch rethrows the exception, then those changes will be propagated only if the exception specifier is a reference:

    17.1.5. 捕獲所有異常的句柄 The Catch-All Handler

    格式:

    // matches any exception that might be thrown

    catch (...) {

        // place our code here

    }

    如果catch(...)和其它的catch語句結合起來使用,它必須在最后,否則跟在catch(...)后面的任何catch語句都不會被匹配執行。If a catch(...) is used in combination with other catch clauses, it must be last; otherwise, any catch clause that followed it could never be matched.

    17.1.6. 函數try程序塊與構造函數Function Try Blocks and Constructors

    這是為了捕捉構造函數初始化式constructor initializer中的異常。因為構造函數的函數體里的catch語句是不能管理發生在構造函數初始化式執行過程中的異常的。A catch clause inside the constructor body cannot handle an exception that might occur while processing a constructor initializer.

    function try block

    template <class T> Handle<T>::Handle(T *p)

    try : ptr(p), use(new size_t(1)) //要把初始化式包含在try程序塊里

    {

         // empty function body

    } catch(const std::bad_alloc &e)

           { handle_out_of_memory(e); }

    17.1.7. 異常類層次Exception Class Hierarchies

    標準庫異常類。基類excepion,其只是定義了虛函數what()

    關注由excepion派生出來的兩種異常類:runtime_errorlogic_error

    定義應用級的異常類Exception Classes

    class isbn_mismatch: public std::logic_error {

    public:

        explicit isbn_mismatch(const std::string &s): std::logic_error(s) { }

        isbn_mismatch(const std::string &s,

            const std::string &lhs, const std::string &rhs):

            std::logic_error(s), left(lhs), right(rhs) { }

        const std::string left, right;

        virtual ~isbn_mismatch() throw() { } // 解釋見:Section 17.1.10

    };

    使用自定義異常:

    Sales_item

    operator+(const Sales_item& lhs, const Sales_item& rhs)

    {

        if (!lhs.same_isbn(rhs))

            throw isbn_mismatch("isbn mismatch",

                                lhs.book(), rhs.book());

        Sales_item ret(lhs); // copy lhs into a local object that we'll return

        ret += rhs;           // add in the contents of rhs

        return ret;           // return ret by value

    }

    17.1.8. 自動資源釋放Automatic Resource Deallocation

    問題提出:

    void f()

     {

         vector<string> v;                   // local vector

         string s;

         while (cin >> s)

             v.push_back(s);                 // populate the vector

         string *p = new string[v.size()];   // dynamic array

         // 如果在這個地方拋出異常,那么動態分配的數組所占用的內存就無法釋放

         delete [] p;

     }  

    用類管理資源分配(Using Classes to Manage Resource Allocation)

    簡單地說就是使用定義類封裝資源的獲得和釋放。(define a class to encapsulate the acquisition and release of a resource.

    原型:

    class Resource {

     public:

         Resource(parms p): r(allocate(p)) { }

         ~Resource() { release(r); }

         // also need to define copy and assignment

     private:

         resource_type *r;           // resource managed by this type

         resource_type *allocate(parms p);     // allocate this resource

         void release(resource_type*);         // free this resource

     };

    調用方法:

    void fcn()

     {

        Resource res(args);   // allocates resource_type

        // code that might throw an exception

        ...

     } 

    結論就是不要讓動態分配的資源“裸奔”L

    17.1.9. auto_ptr The auto_ptr Class

    auto_ptr 類的限制:

    只能管理一個對象,不能管理數組。An auto_ptr may hold only a pointer to an object and may not be used to point to a dynamically allocated array.

    對照來看安全和不安全的寫法:

    不安全:

    void f()

     {

         int *ip = new int(42);     // dynamically allocate a new object

         // 如果此時拋出異常,ip指向的內存不會被釋放

         delete ip;                 // return the memory before exiting

     }

    安全:

    void f()

     {

         auto_ptr<int> ap(new int(42)); // allocate a new object

         // code that throws an exception that is not caught inside f

     }

    定義auto_ptr 對象:

    auto_ptr<int> pi(new int(1024));    //實參是new表達式返回的對象

    使用auto_ptr Using an auto_ptr

    由于auto_ptr重載了解引用和箭頭操作符,因此使用auto_ptr和使用一般的指針類似。The auto_ptr class defines overloaded versions of the dereference (*) and arrow (->) operators (Section 14.6, p. 523). Because auto_ptr defines these operators, we can use an auto_ptr in some ways that are similar to using a built-in pointer:

    auto_ptr的拷貝和賦值操作都是破壞性的操作

    之所以這么說,是因為當拷貝一個auto_ptr或者賦值給令一個auto_ptr 時,潛在的對象的所有者就從最初的傳遞給copy結果一方。最初的auto_ptr 被重置為非綁定狀態。When we copy an auto_ptr or assign its value to another auto_ptr, ownership of the underlying object is transferred from the original to the copy. The original auto_ptr is reset to an unbound state.

    測試auto_ptr

    get方法返回的auto_ptr包含的潛在的指針。

    if (p_auto.get())

     *p_auto = 1024;

    Reset操作

    不能對指針進行地址賦值,要通過reset操作來修改指針。從而是auto_ptr綁定到一個新的指針上。

    p_auto.reset(new int(1024));

    調用auto_ptr reset,在auto_ptr綁定到另一個對象之前,刪除auto_ptr 指向的對象。(如果有的話)Calling reset on an auto_ptr deletes the object (if any) to which the auto_ptr refers before binding the auto_ptr to another object.

    17.1.10. 異常說明Exception Specifications

    定義異常說明

    void recoup(int) throw(runtime_error);

    在編譯的時候,編譯器不能也不會試圖驗證異常說明。The compiler cannot and does not attempt to verify exception specifications at compile time.

    異常說明和析構函數

    標準的exception類希望析構函數不拋出任何的異常。可以參看17.1.2析構函數不能拋出異常。

    class isbn_mismatch: public std::logic_error {

     public:

         virtual ~isbn_mismatch() throw() { }

     };

    虛析構函數:即使是基類指針,也會調用派生類的析構函數。如果析構函數為虛函數,那么通過指針調用時,運行哪個析構函數將因指針所指對象類型的不同而不同。(唉,復習一下,就是忽然掰不開了)

    異常說明和虛函數

    基類的虛函數可以有不同于派生類的異常說明。然而派生類的虛函數的異常說明必須等于基類的虛函數的異常說明,或者更加嚴格。A virtual function in a base class may have an exception specification that differs from the exception specification of the corresponding virtual in a derived class. However, the exception specification of a derived-class virtual function must be either equally or more restrictive than the exception specification of the corresponding base-class virtual function.

    KAO,直說啊,就是派生類不能增加新的異常。

    基類:

    class Base {

     public:

         virtual double f1(double) throw ();

         virtual int f2(int) throw (std::logic_error);

         virtual std::string f3() throw (std::logic_error, std::runtime_error);

     };

    派生類:

    class Derived : public Base {

    public:

         //基類的f1函數不拋出異常,派生類拋出underflow_error異常,這就叫做派生類的異常說明沒有基類的異常說明限制線強(less restrictive),所以錯誤!就是說派生類不能增加新的異常。

         double f1(double) throw (std::underflow_error);

         // ok: same exception specification as Base::f2

         int f2(int) throw (std::logic_error);

         // ok: Derived f3 is more restrictive

         std::string f3() throw ();

     };

    基類的異常列表是派生類的虛函數可能會拋出異常的超集。Our code can rely on the fact that the list of exceptions in the base class is a superset of the list of exceptions that a derived-class version of the virtual might throw.

    17.1.11. 函數指針的異常說明Function Pointer Exception Specifications

    異常說明可以看做是函數類型的一部分,因此如果定義函數指針,這個函數指針也要提供異常說明的定義:

    void (*pf)(int) throw(runtime_error);

    如果不指定異常說明,這種指針就是指向拋出任何類型異常的匹配函數。If no specification is provided, then the pointer may point at a function with matching type that could throw any kind of exception.

    當指向帶有異常說明的函數指針由另一個指針初始化時,兩個指針的異常說明可以不一致。但是源指針的說明至少和目標指針的顯著性相同。When a pointer to function with an exception specification is initialized from (or assigned to) another pointer (or to the address of a function), the exception specifications of both pointers do not have to be identical. However, the specification of the source pointer must be at least as restrictive as the specification of the destination pointer

    void recoup(int) throw(runtime_error);

    // ok: recoup is as restrictive as pf1

    void (*pf1)(int) throw(runtime_error) = recoup;

    // ok: recoup is more restrictive than pf2

    void (*pf2)(int) throw(runtime_error, logic_error) = recoup;

    // error: recoup is less restrictive than pf3

    void (*pf3)(int) throw() = recoup;

    // ok: recoup is more restrictive than pf4

    void (*pf4)(int) = recoup;

    把函數指針的比較關系列成下表:

    logic runtime_error _error -> runtime_error -> throw() 越來越more restrictive

    recoup

    pf

    runtime_error

    runtime_error, logic_error

    recoup is more restrictive than pf2

    runtime_error

    throw()

    recoup is less restrictive than pf3

    17.2  命名空間Namespaces

    命名空間可以看做是Java中的package的概念嗎?

    一個命名空間就是一個作用域。A namespace is a scope.

    17.2.1. 命名空間的定義Namespace Definitions

    定義

    注意不能以分號結束。

    關鍵字namespace

    namespace cplusplus_primer {

         class Sales_item { /* ... */};

         Sales_item operator+(const Sales_item&,

                              const Sales_item&);

         class Query {

         public:

             Query(const std::string&);

             std::ostream &display(std::ostream&) const;

             // ...

         };

         class Query_base { /* ... */};

     }

    可以出現在命名空間的,包括:

    變量

    函數

    模板

    其它命名空間

    以上這些都可以叫做的命名空間成員namespace members

    在命名空間外使用命名空間的成員

    namespace_name::member_name

    或者

    using cplusplus_primer::Query;

    命名空間可以是不連續的定義

    這樣可以按管理類和函數定義的相同的方式來組織命名空間。a namespace can be organized in the same way that we manage our own class and function definitions

    1.      定義類的命名空間成員,以及作為類接口的一部分的函數聲明與對象聲明,可以放在頭文件中,使用命名空間成員的文件可以包含這些頭文件。

    2.      命名空間成員的定義可以放在單獨的文件里。

    // ---- Sales_item.h ----

    namespace cplusplus_primer {

        class Sales_item { /* . . . */};

        Sales_item operator+(const Sales_item&,

                             const Sales_item&);

        // declarations for remaining functions in the Sales_item interface

    }

    // ---- Sales_item.cc ----

    namespace cplusplus_primer {

    // definitions for Sales_item members and overloaded operators

    }

    17.2.2. 嵌套命名空間Nested Namespaces

    嵌套命名空間也遵守同樣的規則,這些規則包括:

    1.      在一個封裝的命名空間內聲明的名字會被同名的名字隱藏,這個同名的名字聲明在一個嵌套的命名空間里。Names declared in an enclosing namespace are hidden by declarations of the same name in a nested namespace.

    2.      對于命名空間來說,在其嵌套命名空間里定義的名字是局部的。Names defined inside a nested namespace are local to that namespace.

    3.      在封裝的命名空間之外的代碼如果想要引用嵌套命名空間里的名字,則必須通過限定詞。Code in the outer parts of the enclosing namespace may refer to a name in a nested namespace only through its qualified name.

    17.2.3. 未命名的命名空間Unnamed Namespaces

    對于特殊的文件來說,未命名的命名空間是局部的,并且也不能跨多個文本文件。但是在同一個文件中,它可以是不連續的。the definition of an unnamed namespace is local to a particular file and never spans multiple text files.

    未命名的命名空間一般用來定義實體,這些實體對于所在的文件來說是局部實體。定義在未命名的命名空間里的名字只對包含這個命名空間的文件可見。Names defined in an unnamed namespace are visible only to the file containing the namespace.

    定義在未命名空間里的名字可以在定義這個未命名空間的命名空間中找到。Names defined in an unnamed namespace are found in the same scope as the scope at which the namespace is defined.

    以上這個規則就是造成下面的錯誤原因:

    int i;   // global declaration for i

    namespace {

        int i;

    }

    // error: ambiguous defined globally and in an unnested, unnamed namespace

    i = 10;

    但是如果這樣:

    Namespace local {

    int i;   // global declaration for i

    namespace {

        int i;

    }

    //error在這里引用i是錯誤的,引起二義性

    i = 10;

    }

    // 在這里引用I OK

    loca:i = 10;

    loca::i = 10;

    17.2.4. 命名空間成員的使用Using Namespace Members

    using 聲明的作用域Scope of a using Declaration

    名字從using聲明開始直到包含該聲明的作用域結束都是可見的。外部作用域中定義的同名實體被屏蔽。The name is visible from the point of the using declaration to the end of the scope in which the declaration is found. Entities with the same name defined in an outer scope are hidden.

    命名空間別名

    很簡單,就是一個同義詞:

    namespace primer = cplusplus_primer;

    namespace Qlib = cplusplus_primer::QueryLib;

    using 指示(using Directives

    基本形式:

    using namespace A;

    using聲明和using指示區別是啥?

    using聲明將名字直接放入其出現的作用域中。using聲明就好像是命名空間成員的局部別名。A using declaration puts the name directly in the same scope in which the using declaration itself appears. It is as if the using declaration is a local alias for the namespace member. 

    using指示不是聲明一個命名空間成員名稱的別名。using指示具有的作用是:提升命名空間成員到最近的作用域。這樣這個作用域中既包含它自己的命名空間還包含using指示指向的命名空間。A using directive does not declare local aliases for the namespace member names. Rather, it has the effect of lifting the namespace members into the nearest scope that contains both the namespace itself and the using directive.

    這個說法很讓人暈。看看后面大師給的example,杠杠的J即使可以看明白,我還是認為這些說法就如同孔乙己關于回字的N種寫法一樣,沒有實用性。

    后面一段,大師也是說慎用using指示,還是用using聲明好些。

    namespace blip {

         int bi = 16, bj = 15, bk = 23;

         // other declarations

     }

     int bj = 0; // ok: bj inside blip is hidden inside a namespace

     void manip()

     {

          // using directive - names in blip "added" to global scope

          // (提升命名空間成員到最近的作用域)

          using namespace blip;

          ++bi;           // sets blip::bi to 17

          ++bj;           // error: ambiguous, global bj or blip::bj?

          ++::bj;         // ok: sets global bj to 1

          ++blip::bj;     // ok: sets blip::bj to 16

          int bk = 97;    // local bk hides blip::bk

          ++bk;           // sets local bk to 98

     }

    17.2.5. 類、命名空間和作用域Classes, Namespaces, and Scope

    尋址方式

    名字尋址

    當尋址一個名字時,我們向封閉的作用域外尋找。對于一個命名空間里的名字,它封閉的作用域或許是一個或者多個嵌套命名空間,這些命名空間最終結束于一個完全封閉的全局命名空間。只考慮在使用前就已經聲明的名字,并且這些名字在塊里依然是可見的。When looking for a name, we look outward through the enclosing scopes. An enclosing scope for a name used inside a namespace might be one or more nested namespaces ending finally with the all-encompassing global namespace. Only names that have been declared before the point of use that are in blocks that are still open are considered

    類尋址

    當一個名字在類作用域里使用時間,我們首先在成員自身里尋找,這包括基類。只有找遍類,我們才在封閉的作用域里繼續尋找。當一個類封裝在一個作用域里時,還要遵循同樣的規則:首先是成員,然后是類,然后是在封閉的作用域,一個或者多個命名空間。When a name is used in a class scope, we look first in the member itself, then in the class, including any base classes. Only after exhausting the class(es) do we examine the enclosing scopes. When a class is wrapped in a namespace, the same lookup happens: Look first in the member, then the class (including base classes), then look in the enclosing scopes, one or more of which might be a namespace:

    依賴于實參的查找和類類型形參

    函數,包括重載操作符,形參是類類型(或者是類類型的指針或引用),并且函數和形參是定義在同一個命名空間,當類類型的對象當做是實參時,這樣函數是可見的。Functions, including overloaded operators, that take parameters of a class type (or pointer or reference to a class type), and that are defined in the same namespace as the class itself, are visible when an object of (or reference or pointer to) the class type is used as an argument.

    下面這個例子getline()無需顯式調用std::getline()。因為作為實參的sgetline函數是在同一個命名空間里

    std::string s;

        // ok: calls std::getline(std::istream&, const std::string&)

        getline(std::cin, s);

    隱式友元聲明和命名空間

    如果沒有可見的聲明,那么友元聲明具有把函數或者類聲明放在作用域的效果。如果類是定義在命名空間里的,那么在同一個命名空間里, 就會聲明一個不同的未聲明的友元函數。If there isn't a declaration already visible, then the friend declaration has the effect of putting a declaration for that function or class into the surrounding scope. If a class is defined inside a namespace, then an otherwise undeclared friend function is declared in the same namespace

    聲明:

    namespace A {

         class C {

             friend void f(const C&); // makes f a member of namespace A

         };

     }

    使用:

    在這里f函數調用時無需使用限定詞,是因為前面所講的“依賴實參查找”的規則。

    // f2 defined at global scope

    void f2()

    {

         A::C cobj;

         f(cobj); // calls A::f

    }

    17.2.6. 重載與命名空間Overloading and Namespaces

    備選函數和命名空間

    命名空間在確定備選函數上有兩方面的影響:

    1. using聲明和using指示可以增加備選函數。

    2. 如果函數的形參是類類型,那么要在每個定義了這個類(或者是這個類的基類)的命名空間尋找備選函數。在這些命名空間里的函數只要名字和調用的函數相同都要作為備選函數。Each namespace that defines a class used as a parameter (and those that define its base class(es)) is searched for candidate functions. Any functions in those namespaces that have the same name as the called function are added to the candidate set.

    Example

    namespace NS {

        class Item_base { /* ... */ };

        void display(const Item_base&) { }

    }

    class Bulk_item : public NS::Item_base { };

    int main() {

    Bulk_item book1;

    //display的備選函數還應該包括命名空間NS,因為基類Item_base是在NS中定義的。

        display(book1);

        return 0;

    }

    重載和using聲明

    如果函數在命名空間內重載,那么using聲明聲明了這個函數的所有版本。If a function is overloaded within a namespace, then a using declaration for the name of that function declares all the functions with that name.

    為了保證命名空間的接口不沖突,Using聲明合并重載函數的所有版本。A using declaration incorporates all versions of an overloaded function to ensure that the interface of the namespace is not violated.

    using聲明所在的作用域內,using聲明引入的函數重載了其它函數聲明。The functions introduced by a using declaration overload any other declarations of the functions with the same name already present in the scope where the using declaration appears.

    如果using聲明所在作用域內已經有同名的函數,并且這個函數的參數列表也是相同的,那么這個using聲明就是錯誤的。If the using declaration introduces a function in a scope that already has a function of the same name with the same parameter list, then the using declaration is in error.

    重載與 using 指示Overloading and using Directives

    跨越多個 using 指示的重載Overloading across Multiple using Directives

    如果出現多個using指示,每個命名空間的名字都成為候選集合的一部分。If many using directives are present, then the names from each namespace become part of the candidate set.

    17.2.7. 命名空間與模板Namespaces and Templates

    模板的顯式特化必須在通用模板定義的命名空間內聲明。否則特化的名字要和模板的名字不同。An explicit specialization of a template must be declared in the namespace in which the generic template is defined. Otherwise, the specialization would have a different name than the template it specialized.

    17.3 Multiple and Virtual Inheritance

    17.3.1. 多重繼承Multiple Inheritance

    定義:

    class Panda : public Bear, public Endangered {

    };

    派生構造函數初始化所有的基類

    基類的構造函數的調用順序是基類出現在類派生列表中的順序。The base-class constructors are invoked in the order in which they appear in the class derivation list.

    The order of constructor invocation is not affected by whether the base class appears in the constructor initializer list or the order in which base classes appear within that list.

    17.3.2. 轉換與多個基類Conversions and Multiple Base Classes

    指向派生類的指針或引用能夠轉換成任何基類的指針或引用。A pointer or reference to a derived class can be converted to a pointer or reference to any of its base classes.

    使用基類指針不允許訪問其它基類的成員。Using a pointer to one base does not allow access to members of another base.

    確定使用哪個虛析構函數

    假設所有的基類都恰當的定義了它們的析構函數是虛函數,那么不管指向的刪除的對象的指針是何種類型,虛函數的處理都是一致的。Assuming all the root base classes properly define their destructors as virtual, then the handling of the virtual destructor is consistent regardless of the pointer type through which we delete the object

    下面的例子就是對這段話的最好的說明。假設下面的這些指針都是指向Panda對象的,析構函數執行時,具有完全相同的執行順序。Assuming each of these pointers points to a Panda object, the exact same order of destructor invocations occurs in each case.

    delete pz; // pz is a ZooAnimal*

    delete pb; // pb is a Bear*

    delete pp; // pp is a Panda*

    delete pe; // pe is a Endangered*

    17.3.3. 多重繼承派生類的復制控制

    此前的Panda的定義:

    class Panda : public Bear, public Endangered {

     };

    因此copy的順序:ZooAnimal, Bear, Endangered.

    17.3.4. 多重繼承下的類作用域(Class Scope under Multiple Inheritance

    這部分其實要說明的是如何進行名字查找(name lookup)。

    在單繼承下,如果在一個成員函數中查找一個名字,先在函數自身中查找,然后在函數所在的類中查找,再到基類中查找。

    但是在多重繼承下,就會存在并發地在多個基類中查找。

    //ying_yang is Panda’s object

    ying_yang.print(cout); //error

    ying_yang.Endangered::print(cout); //OK.

    當一個類有多個基類時,名字查找同時發生在所有直接基類。因此就有可能一個多繼承的派生類從兩個或者多個基類中繼承了同名的成員。此時,若不使用限定詞就會產生二義性。When a class has multiple base classes, name lookup happens simultaneously through all the immediate base classes. It is possible for a multiply derived class to inherit a member with the same name from two or more base classes. Unqualified uses of that name are ambiguous.

    并且,即使連個基類中的函數同名但是形參列表不一致時,也會產生二義性。因為名字查找總是在先(Name Lookup Happens First)。就是說編譯器找到兩個名字相同的就已經翹了。

    避免用戶級的二義性(Avoiding User-Level Ambiguities

    避免潛在二義性的最好的方法是在派生類里定義函數版本The best way to avoid potential ambiguities is to define a version of the function in the derived class that resolves the ambiguity.

    std::ostream& Panda::print(std::ostream &os) const

     {

         Bear::print(os);        // print the Bear part

         Endangered::print(os); // print the Endangered part

         return os;

     }

    17.3.5. 虛繼承Virtual Inheritance

    在多繼承關系中,基類有可能在派生類中出現多次。

    舉個例子:istreamostreamios的派生類,iostream繼承了istreamostream,這就意味著iostream包含兩個ios的對象。問題來了L這是不可以的。

    解決辦法就是虛繼承(virtual inheritance)。

    虛繼承是一種機制:類指定共享它的基類的狀態。在虛繼承下,不管在派生類的繼承層次關系中虛基類出現多少次,只共享一個給定的虛基類的子對象。共享的基類子對象稱為虛基類。Virtual inheritance is a mechanism whereby a class specifies that it is willing to share the state of its virtual base class. Under virtual inheritance, only one, shared base-class subobject is inherited for a given virtual base regardless of how many times the class occurs as a virtual base within the derivation hierarchy. The shared base-class subobject is called a virtual base class.

    定義:

    class istream : public virtual ios { ... };

    class ostream : virtual public ios { ... };

    // iostream inherits only one copy of its ios base class

    class iostream: public istream, public ostream { ... };

    17.3.6. 虛基類的聲明Virtual Base Class Declaration

    虛基類成員的可見性

    共享虛基類的成員可以直接、無二義性的訪問。類似地,如果虛基類的一個成員只沿著一個派生路徑重新定義,那么重新定義的成員可以直接訪問。在非虛派生下,任何一種訪問都是具有二義性的。Members in the shared virtual base can be accessed unambiguously and directly. Similarly, if a member from the virtual base is redefined along only one derivation path, then that redefined member can be accessed directly. Under a nonvirtual derivation, both kinds of access would be ambiguous.

    17.3.7. 特殊的初始化語義(Special Initialization Semantics

    .因為是多重繼承,就存在基類被多次初始化的問題。因此需要特殊處理。

    虛繼承,最末層的派生類(most derived class)的構造函數初始化虛基類。In a virtual derivation, the virtual base is initialized by the most derived constructor.

    雖然虛基類是被最末層的派生類初始化的,任何直接或者簡潔繼承虛基類的類都必須提供自己的基類初始化式。只要創建虛基類的派生類類型的對象,派生類必須初始化它的虛基類。這些初始化式只有當我們初始化中間類型的對象時才用到。Although the virtual base is initialized by the most derived class, any classes that inherit immediately or indirectly from the virtual base usually also have to provide their own initializers for that base. As long as we can create independent objects of a type derived from a virtual base, that class must initialize its virtual base. These initializers are used only when we create objects of the intermediate type.

    很拗口。看例子就easy to easy

    BearRaccoonPanda都定義構造函數初始化虛基類ZooAnimalBearRaccoon的初始化式只有在創建BearRaccoon類型的對象時才用到。如果創建Panda對象,只會調用Panda的初始化式。

    Bear::Bear(std::string name, bool onExhibit)

          : ZooAnimal(name, onExhibit, "Bear") { }

    Raccoon::Raccoon(std::string name, bool onExhibit)

           : ZooAnimal(name, onExhibit, "Raccoon") { }

    Panda::Panda(std::string name, bool onExhibit)

           : ZooAnimal(name, onExhibit, "Panda"),

             Bear(name, onExhibit),

             Raccoon(name, onExhibit),

             Endangered(Endangered::critical),

             sleeping_flag(false) { }

    構造函數與析構函數次序

    不管虛基類出現在繼承層次關系的哪部分,虛基類總是優先于非虛基類構造。Virtual base classes are always constructed prior to nonvirtual base classes regardless of where they appear in the inheritance hierarchy.

    還是看例子,一目了然,TeddyBearBookCharacterBearToyAnimal派生而來。Bear是虛基類ZooAnimal的派生類,這樣TeddyBear就存在連個虛基類:ToyAnimalZooAnimal

    class Character { /* ... */ };

    class BookCharacter : public Character { /* ... */ };

    class ToyAnimal { /* ... */ };

    class TeddyBear : public BookCharacter,

                      public Bear, public virtual ToyAnimal

                      { /* ... */ };

    TeddyBear構造函數的調用次序就是:先虛基類,后非虛基類。

    ZooAnimal();           // Bear's virtual base class

    ToyAnimal();           // immediate virtual base class

    Character();           // BookCharacter's nonvirtual base class

    BookCharacter();       // immediate nonvirtual base class

    Bear();                // immediate nonvirtual base class

    TeddyBear();           // most derived class

    析構函數相反。

    posted on 2009-08-12 11:10 amenglai 閱讀(1209) 評論(0)  編輯  收藏 所屬分類: C++ Primer 之 讀書筆記

    主站蜘蛛池模板: 日韩免费人妻AV无码专区蜜桃| 成人影片麻豆国产影片免费观看 | 亚洲人成色77777| 四虎影视成人永久免费观看视频 | 成人毛片18女人毛片免费视频未| 亚洲欧美在线x视频| 亚洲av中文无码乱人伦在线咪咕| 成年在线观看网站免费| fc2成年免费共享视频18| 777亚洲精品乱码久久久久久 | 四虎国产精品免费久久影院| 久久99精品国产免费观看| 亚洲国产精品成人AV在线| 久久国产亚洲精品麻豆| 热99re久久免费视精品频软件 | 久久WWW色情成人免费观看| XXX2高清在线观看免费视频| 亚洲国产成人精品激情| 精品亚洲综合在线第一区| 巨胸喷奶水视频www网免费| 人人玩人人添人人澡免费| 处破女第一次亚洲18分钟| 亚洲国产精品成人精品软件| 黑人大战亚洲人精品一区| 四虎免费大片aⅴ入口| 污污网站免费观看| 一级特黄录像免费播放中文版| 亚洲AV一二三区成人影片| 久久国产亚洲精品麻豆| 免费夜色污私人影院在线观看| av无码免费一区二区三区| 国内精品免费久久影院| 国内成人精品亚洲日本语音 | 亚洲日本香蕉视频观看视频| 国产自偷亚洲精品页65页| 国产hs免费高清在线观看| 久久天天躁狠狠躁夜夜免费观看| 久久99毛片免费观看不卡| 一级特黄录像视频免费| 黄网站色成年片大免费高清| 亚洲精品欧美综合四区 |