C++ 中 子類轉父類 和 父類轉子類 的方法 ---


15.3. Conversions and Inheritance

15.3. 轉換與繼承

Understanding conversions between base and derived types is essential to understanding how object-oriented programming works in C++.

理解基類類型和派生類型之間的轉換,對於理解面向對象編程在 C++ 中如何工作非常關鍵。



As we've seen, every derived object contains a base part, which means that we can execute operations on a derived object as if it were a base object. Because a derived object is also a base, there is an automatic conversion from a reference to a derived type to a reference to its base type(s). That is, we can convert a reference to a derived object to a reference to its base subobject and likewise for pointers.

我們已經看到,每個派生類對象包含一個基類部分,這意味着可以像使用基類對象一樣在派生類對象上執行操作。因爲派生類對象也是基類對象,所以存在從派生類型引用到基類類型引用的自動轉換,即,可以將派生類對象的引用轉換爲基類子對象的引用,對指針也類似。

Base-type objects can exist either as independent objects or as part of a derived object. Therefore, a base object might or might not be part of a derived object. As a result, there is no (automatic) conversion from reference (or pointer) to base to reference (or pointer) to derived.

基類類型對象既可以作爲獨立對象存在,也可以作爲派生類對象的一部分而存在,因此,一個基類對象可能是也可能不是一個派生類對象的部分,結果,沒有從基類引用(或基類指針)到派生類引用(或派生類指針)的(自動)轉換。

The situation with respect to conversions of objects (as opposed to references or pointers) is more complicated. Although we can usually use an object of a derived type to initialize or assign an object of the base type, there is no direct conversion from an object of a derived type to an object of the base type.

相對於引用或指針而言,對象轉換的情況更爲複雜。雖然一般可以使用派生類型的對象對基類類型的對象進行初始化或賦值,但,沒有從派生類型對象到基類類型對象的直接轉換。

15.3.1. Derived-to-Base Conversions

15.3.1. 派生類到基類的轉換

If we have an object of a derived type, we can use its address to assign or initialize a pointer to the base type. Similarly, we can use a reference or object of the derived type to initialize a reference to the base type. Pedantically speaking, there is no similar conversion for objects. The compiler will not automatically convert an object of derived type into an object of the base type.

如果有一個派生類型的對象,則可以使用它的地址對基類類型的指針進行賦值或初始化。同樣,可以使用派生類型的引用或對象初始化基類類型的引用。嚴格說來,對對象沒有類似轉換。編譯器不會自動將派生類型對象轉換爲基類類型對象。

It is, however, usually possible to use a derived-type object to initialize or assign an object of base type. The difference between initializing and/or assigning an object and the automatic conversion that is possible for a reference or pointer is subtle and must be well understood.

但是,一般可以使用派生類型對象對基類對象進行賦值或初始化。對對象進行初始化和/或賦值以及可以自動轉換引用或指針,這之間的區別是微妙的,必須好好理解。

Conversion to a Reference is Not the Same as Converting an Object
引用轉換不同於轉換對象

As we've seen, we can pass an object of derived type to a function expecting a reference to base. We might therefore think that the object is converted. However, that is not what happens. When we pass an object to a function expecting a reference, the reference is bound directly to that object. Although it appears that we are passing an object, the argument is actually a reference to that object. The object itself is not copied and the conversion doesn't change the derived-type object in any way. It remains a derived-type object.

我們已經看到,可以將派生類型的對象傳給希望接受基類引用的函數。也許會因此認爲對象進行轉換,但是,事實並非如此。將對象傳給希望接受引用的函數時,引用直接綁定到該對象,雖然看起來在傳遞對象,實際上實參是該對象的引用,對象本身未被複制,並且,轉換不會在任何方面改變派生類型對象,該對象仍是派生類型對象。

When we pass a derived object to a function expecting a base-type object (as opposed to a reference) the situation is quite different. In that case, the parameter's type is fixedboth at compile time and run time it will be a base-type object. If we call such a function with a derived-type object, then the base-class portion of that derived object is copied into the parameter.

將派生類對象傳給希望接受基類類型對象(而不是引用)的函數時,情況完全不同。在這種情況下,形參的類型是固定的——在編譯時和運行時形參都是基類類型對象。如果用派生類型對象調用這樣的函數,則該派生類對象的基類部分被複制到形參。

It is important to understand the difference between converting a derived object to a base-type reference and using a derived object to initialize or assign to a base-type object.

一個是派生類對象轉換爲基類類型引用,一個是用派生類對象對基類對象進行初始化或賦值,理解它們之間的區別很重要。

Using a Derived Object to Initialize or Assign a Base Object
用派生類對象對基類對象進行初始化或賦值

When we initialize or assign an object of base type, we are actually calling a function: When we initialize, we're calling a constructor; when we assign, we're calling an assignment operator.

對基類對象進行初始化或賦值,實際上是在調用函數:初始化時調用構造函數,賦值時調用賦值操作符。

When we use a derived-type object to initialize or assign a base object, there are two possibilities. The first (albeit unlikely) possibility is that the base class might explicitly define what it means to copy or assign an object of the derived type to an object of the base type. It would do so by defining an appropriate constructor or assignment operator:

用派生類對象對基類對象進行初始化或賦值時,有兩種可能性。第一種(雖然不太可能的)可能性是,基類可能顯式定義了將派生類型對象複製或賦值給基類對象的含義,這可以通過定義適當的構造函數或賦值操作符實現:

     class Derived;
     class Base {
     public:
         Base(const Derived&);  // create a new Base from a Derived
         Base &operator=(const Derived&);  // assign from a Derived
         // ...
     };

In this case, the definition of these members would control what happens when aDerived object is used to initialize or assign to aBase object.

在這種情況下,這些成員的定義將控制用 Derived 對象對 Base 對象進行初始化或賦值時會發生什麼。

However, it is uncommon for classes to define explicitly how to initialize or assign an object of the base type from an object of derived type. Instead, base classes ususally define (either explicitly or implicitly) their own copy constructor and assignment operator (Chapter 13). These members take a parameter that is a (const) reference to the base type. Because there is a conversion from reference to derived to reference to base, these copy-control members can be used to initialize or assign a base object from a derived object:

然而,類顯式定義怎樣用派生類型對象對基類類型進行初始化或賦值並不常見,相反,基類一般(顯式或隱式地)定義自己的複製構造函數和賦值操作符(第十三章),這些成員接受一個形參,該形參是基類類型的(const)引用。因爲存在從派生類引用到基類引用的轉換,這些複製控制成員可用於從派生類對象對基類對象進行初始化或賦值:

     Item_base item; // object of base type
     Bulk_item bulk; // object of derived type
     // ok: uses Item_base::Item_base(const Item_base&) constructor
     Item_base item(bulk);  // bulk is "sliced down" to its Item_base portion
     // ok: calls Item_base::operator=(const Item_base&)
     item = bulk;           // bulk is "sliced down" to its Item_base portion

When we call the Item_base copy constructor or assignment operator on an object of typeBulk_item, the following steps happen:

Bulk_item 類型的對象調用 Item_base 類的複製構造函數或賦值操作符時,將發生下列步驟:

  • The Bulk_item object is converted to a reference to Item_base, which means only that an Item_base reference is bound to theBulk_item object.

    Bulk_item 對象轉換爲 Item_base 引用,這僅僅意味着將一個 Item_base 引用綁定到 Bulk_item 對象。

  • That reference is passed as an argument to the copy constructor or assignment operator.

    將該引用作爲實參傳給複製構造函數或賦值操作符。

  • Those operators use the Item_base part of Bulk_item to initialize and assign, respectively, the members of theItem_base on which the constructor or assignment was called.

    那些操作符使用 Bulk_itemItem_base 部分分別對調用構造函數或賦值的Item_base 對象的成員進行初始化或賦值。

  • Once the operator completes, the object is an Item_base. It contains a copy of theItem_base part of theBulk_item from which it was initialized or assigned, but theBulk_item parts of the argument are ignored.

    一旦操作符執行完畢,對象即爲 Item_base。它包含 Bulk_item Item_base 部分的副本,但實參的 Bulk_item 部分被忽略。

In these cases, we say that the Bulk_item portion of bulk is "sliced down" as part of the initialization or assignment to item. AnItem_base object contains only the members defined in the base class. It does not contain the members defined by any of its derived types. There is no room in anItem_base object for the derived members.

在這種情況下,我們說 bulkBulk_item 部分在對 item 進行初始化或賦值時被“切掉”了。Item_base 對象只包含基類中定義的成員,不包含由任意派生類型定義的成員,Item_base 對象中沒有派生類成員的存儲空間。

Accessibility of Derived-to-Base Conversion
派生類到基類轉換的可訪問性

Like an inherited member function, the conversion from derived to base may or may not be accessible. Whether the conversion is accessible depends on the access label specified on the derived class' derivation.

像繼承的成員函數一樣,從派生類到基類的轉換可能是也可能不是可訪問的。轉換是否訪問取決於在派生類的派生列表中指定的訪問標號。

 

To determine whether the conversion to base is accessible, consider whether apublic member of the base class would be accessible. If so, the conversion is accessible; otherwise, it is not.

要確定到基類的轉換是否可訪問,可以考慮基類的 public 成員是否訪問,如果可以,轉換是可訪問的,否則,轉換是不可訪問的。



If the inheritance is public, then both user code and member functions of subsequently derived classes may use the derived-to-base conversion. If a class is derived usingprivate orprotected inheritance, then user code may not convert an object of derived type to a base type object. If the inheritance isprivate, then classes derived from theprivately inherited class may not convert to the base class. If the inheritance isprotected, then the members of subsequently derived classes may convert to the base type.

如果是 public 繼承,則用戶代碼和後代類都可以使用派生類到基類的轉換。如果類是使用 privateprotected 繼承派生的,則用戶代碼不能將派生類型對象轉換爲基類對象。如果是private 繼承,則從 private 繼承類派生的類不能轉換爲基類。如果是protected 繼承,則後續派生類的成員可以轉換爲基類類型。

Regardless of the derivation access label, a public member of the base class is accessible to the derived class itself. Therefore, the derived-to-base conversion is always accessible to the members and friends of the derived class itself.

無論是什麼派生訪問標號,派生類本身都可以訪問基類的 public 成員,因此,派生類本身的成員和友元總是可以訪問派生類到基類的轉換。

15.3.2. Conversions from Base to Derived

15.3.2. 基類到派生類的轉換

There is no automatic conversion from the base class to a derived class. We cannot use a base object when a derived object is required:

從基類到派生類的自動轉換是不存在的。需要派生類對象時不能使用基類對象:

     Item_base base;
     Bulk_item* bulkP = &base;  // error: can't convert base to derived
     Bulk_item& bulkRef = base; // error: can't convert base to derived
     Bulk_item bulk = base;     // error: can't convert base to derived

The reason that there is no (automatic) conversion from base type to derived type is that a base object might be just thata base. It does not contain the members of the derived type. If we were allowed to assign a base object to a derived type, then we might attempt to use that derived object to access members that do not exist.

沒有從基類類型到派生類型的(自動)轉換,原因在於基類對象只能是基類對象,它不能包含派生類型成員。如果允許用基類對象給派生類型對象賦值,那麼就可以試圖使用該派生類對象訪問不存在的成員。

What is sometimes a bit more surprising is that the restriction on converting from base to derived exists even when a base pointer or reference is actually bound to a derived object:

有時更令人驚訝的是,甚至當基類指針或引用實際綁定到綁定到派生類對象時,從基類到派生類的轉換也存在限制:

     Bulk_item bulk;
     Item_base *itemP = &bulk;  // ok: dynamic type is Bulk_item
     Bulk_item *bulkP = itemP;  // error: can't convert base to derived

The compiler has no way to know at compile time that a specific conversion will actually be safe at run time. The compiler looks only at the static types of the pointer or reference to determine whether a conversion is legal.

編譯器在編譯時無法知道特定轉換在運行時實際上是安全的。編譯器確定轉換是否合法,只看指針或引用的靜態類型。

In those cases when we know that the conversion from base to derived is safe, we can use astatic_cast (Section 5.12.4, p.183) to override the compiler. Alternatively, we could request a conversion that is checked at run time by using adynamic_cast, which is covered inSection 18.2.1 (p.773).

在這些情況下,如果知道從基類到派生類的轉換是安全的,就可以使用 static_cast第 5.12.4 節)強制編譯器進行轉換。或者,可以用dynamic_cast 申請在運行時進行檢查,第 18.2.1 節將介紹dynamic_cast


18.2. Run-Time Type Identification

18.2. 運行時類型識別

Run-time Type Identification (RTTI) allows programs that use pointers or references to base classes to retrieve the actual derived types of the objects to which these pointers or references refer.

通過運行時類型識別(RTTI),程序能夠使用基類的指針或引用來檢索這些指針或引用所指對象的實際派生類型。

RTTI is provided through two operators:

通過下面兩個操作符提供 RTTI:

  1. The typeid operator, which returns the actual type of the object referred to by a pointer or a reference

    typeid 操作符,返回指針或引用所指對象的實際類型。

  2. The dynamic_cast operator, which safely converts from a pointer or reference to a base type to a pointer or reference to a derived type

    dynamic_cast 操作符,將基類類型的指針或引用安全地轉換爲派生類型的指針或引用。

These operators return dynamic type information only for classes with one or more virtual functions. For all other types, information for the static (i.e., compile-time) type is returned.

這些操作符只爲帶有一個或多個虛函數的類返回動態類型信息,對於其他類型,返回靜態(即編譯時)類型的信息。



The RTTI operators execute at run time for classes with virtual functions, but are evaluated at compile time for all other types.

對於帶虛函數的類,在運行時執行 RTTI 操作符,但對於其他類型,在編譯時計算 RTTI 操作符。

Dynamic casts are needed when we have a reference or pointer to a base class but need to perform operations from the derived class that are not part of the base class. Ordinarily, the best way to get derived behavior from a pointer to base is to do so through a virtual function. When we use virtual functions, the compiler automatically selects the right function according to the actual type of the object.

當具有基類的引用或指針,但需要執行不是基類組成部分的派生類操作的時候,需要動態的強制類型轉換。通常,從基類指針獲得派生類行爲最好的方法是通過虛函數。當使用虛函數的時候,編譯器自動根據對象的實際類型選擇正確的函數。

In some situations however, the use of virtual functions is not possible. In these cases, RTTI offers an alternate mechanism. However, this mechanism is more error-prone than using virtual member functions: The programmer mustknow to which type the object should be cast and must check that the cast was performed successfully.

但是,在某些情況下,不可能使用虛函數。在這些情況下,RTTI 提供了可選的機制。然而,這種機制比使用虛函數更容易出錯:程序員必須知道應該將對象強制轉換爲哪種類型,並且必須檢查轉換是否成功執行了。

 

Dynamic casts should be used with caution. Whenever possible, it is much better to define and use a virtual function rather than to take over managing the types directly.

使用動態強制類型轉換要小心。只要有可能,定義和使用虛函數比直接接管類型管理好得多。



18.2.1. The dynamic_cast Operator

18.2.1. dynamic_cast 操作符

The dynamic_cast operator can be used to convert a reference or pointer to an object of base type to a reference or pointer to another type in the same hierarchy. The pointer used with a dynamic_cast must be validit must either be0 or point to an object.

可以使用 dynamic_cast 操作符將基類類型對象的引用或指針轉換爲同一繼承層次中其他類型的引用或指針。與dynamic_cast 一起使用的指針必須是有效的——它必須爲 0 或者指向一個對象。

Unlike other casts, a dynamic_cast involves a run-time type check. If the object bound to the reference or pointer is not an object of the target type, then thedynamic_cast fails. If a dynamic_cast to a pointer type fails, the result of thedynamic_cast is the value 0. If a dynamic_cast to a reference type fails, then an exception of typebad_cast is thrown.

與其他強制類型轉換不同,dynamic_cast 涉及運行時類型檢查。如果綁定到引用或指針的對象不是目標類型的對象,則dynamic_cast 失敗。如果轉換到指針類型的 dynamic_cast 失敗,則 dynamic_cast 的結果是 0 值;如果轉換到引用類型的dynamic_cast 失敗,則拋出一個 bad_cast 類型的異常。

The dynamic_cast operator therefore performs two operations at once. It begins by verifying that the requested cast is valid. Only if the cast is valid does the operator actually do the cast. In general, the type of the object to which the reference or pointer is bound isn't known at compile-time. A pointer to base can be assigned to point to a derived object. Similarly, a reference to base can be initialized by a derived object. As a result, the verification that thedynamic_cast operator performs must be done at run time.

因此,dynamic_cast 操作符一次執行兩個操作。它首先驗證被請求的轉換是否有效,只有轉換有效,操作符才實際進行轉換。一般而言,引用或指針所綁定的對象的類型在編譯時是未知的,基類的指針可以賦值爲指向派生類對象,同樣,基類的引用也可以用派生類對象初始化,因此,dynamic_cast 操作符執行的驗證必須在運行時進行。

Using the dynamic_cast Operator
使用 dynamic_cast 操作符

As a simple example, assume that Base is a class with at least one virtual function and that classDerived is derived from Base. If we have a pointer to Base namedbasePtr, we can cast it at run time to a pointer to Derived as follows:

作爲例子,假定 Base 是至少帶一個虛函數的類,並且 Derived 類派生於 Base 類。如果有一個名爲 basePtr 的指向 Base 的指針,就可以像這樣在運行時將它強制轉換爲指向 Derived 的指針:

     if (Derived *derivedPtr = dynamic_cast<Derived*>(basePtr))
     {
         // use the Derived object to which derivedPtr points
     } else { // BasePtr points at a Base object
         // use the Base object to which basePtr points
     }

At run time, if basePtr actually points to a Derived object, then the cast will be successful, andderivedPtr will be initialized to point to the Derived object to whichbasePtr points. Otherwise, the result of the cast is 0, meaning that derivedPtr is set to 0, and the condition in the if fails.

在運行時,如果 basePtr 實際指向 Derived 對象,則轉換將成功,並且 derivedPtr 將被初始化爲指向 basePtr 所指的 Derived 對象;否則,轉換的結果是 0,意味着將derivedPtr 置爲 0,並且 if 中的條件失敗。

 

We can apply a dynamic_cast to a pointer whose value is 0. The result of doing so is 0.

可以對值爲 0 的指針應用 dynamic_cast,這樣做的結果是 0。



By checking the value of derivedPtr, the code inside theif knows that it is operating on a Derived object. It is safe for that code to useDerived operations. If the dynamic_cast fails because basePtr refers to aBase object, then the else clause does processing appropriate toBase instead. The other advantage of doing the check inside the if condition is that it is not possible to insert code between thedynamic_cast and testing the result of the cast. It is, therefore, not possible to use thederivedPtr inadvertently before testing that the cast was successful. A third advantage is that the pointer is not accessible outside theif. If the cast fails, then the unbound pointer is not available for use in later cases where the test might be forgotten.

通過檢查 derivedPtr 的值,if 內部的代碼知道它是在操作 Derived 對象,該代碼使用Derived 的操作是安全的。如果 dynamic_castbasePtr 引用了 Base 對象而失敗,則else 子句進行適應於 Base 的處理來代替。在 if 條件內部進行檢查的另一好處是,不可能在 dynamic_cast 和測試轉換結果之間插入代碼,因此,不可能在測試轉換是否成功之前不經意地使用 derivedPtr。第三個好處是,在if 外部不能訪問該指針,如果轉換失敗,則在後面的忘了測試的地方,未綁定的指針是不可用的。

 

Performing a dynamic_cast in a condition ensures that the cast and test of its result are done in a single expression.

在條件中執行 dynamic_cast 保證了轉換和其結果測試在一個表達式中進行。



Using a dynamic_cast and Reference Types
使用 dynamic_cast 和引用類型

In the previous example, we used a dynamic_cast to convert a pointer to base to a pointer to derived. Adynamic_cast can also be used to convert a reference to base to a reference to derived. The form for this adynamic_cast operation is the following,

在前面例子中,使用了 dynamic_cast 將基類指針轉換爲派生類指針,也可以使用 dynamic_cast 將基類引用轉換爲派生類引用,這種dynamic_cast 操作的形式如下:

     dynamic_cast< Type& >(val)

where Type is the target type of the conversion, and val is an object of base class type.

這裏,Type 是轉換的目標類型,而 val 是基類類型的對象。

The dynamic_cast operation converts the operand val to the desired typeType& only if val actually refers to an object of the type Type or is an object of a type derived from Type.

只有當 val 實際引用一個 Type 類型對象,或者 val 是一個Type 派生類型的對象的時候,dynamic_cast 操作纔將操作數 val 轉換爲想要的 Type& 類型。

Because there is no such thing as a null reference, it is not possible to use the same checking strategy for references that is used for pointer casts. Instead, when a cast fails, it throws astd::bad_cast exception. This exception is defined in the typeinfo library header.

因爲不存在空引用,所以不可能對引用使用用於指針強制類型轉換的檢查策略,相反,當轉換失敗的時候,它拋出一個 std::bad_cast 異常,該異常在庫頭文件typeinfo 中定義。

We might rewrite the previous example to use references as follows:

可以重寫前面的例子如下,以便使用引用:

     void f(const Base &b)
     {
        try {
            const Derived &d = dynamic_cast<const Derived&>(b);
        // use the Derived object to which b referred
        } catch (bad_cast) {
            // handle the fact that the cast failed
        }
     }


18.2.2. The typeid Operator

18.2.2. typeid 操作符

The second operator provided for RTTI is the typeid operator. Thetypeid operator allows a program to ask of an expression: What type are you?

爲 RTTI 提供的第二個操作符是 typeid 操作符typeid 操作符使程序能夠問一個表達式:你是什麼類型?

A typeid expression has the form

typeid 表達式形如:

     typeid(e)

where e is any expression or a type name.

這裏 e 是任意表達式或者是類型名。

If the type of the expression is a class type and that class contains one or more virtual functions, then the dynamic type of the expression may differ from its static compile-time type. For example, if the expression dereferences a pointer to a base class, then the static compile-time type of that expression is the base type. However, if the pointer actually addresses a derived object, then thetypeid operator will say that the type of the expression is the derived type.

如果表達式的類型是類類型且該類包含一個或多個虛函數,則表達式的動態類型可能不同於它的靜態編譯時類型。例如,如果表達式對基類指針解引用,則該表達式的靜態編譯時類型是基類類型;但是,如果指針實際指向派生類對象,則typeid 操作符將說表達式的類型是派生類型。

The typeid operator can be used with expressions of any type. Expressions of built-in type as well as constants can be used as operands for thetypeid operator. When the operand is not of class type or is a class without virtual functions, then thetypeid operator indicates the static type of the operand. When the operand has a class-type that defines at least one virtual function, then the type is evaluated at run time.

typeid 操作符可以與任何類型的表達式一起使用。內置類型的表達式以及常量都可以用作 typeid 操作符的操作數。如果操作數不是類類型或者是沒有虛函數的類,則typeid 操作符指出操作數的靜態類型;如果操作數是定義了至少一個虛函數的類類型,則在運行時計算類型。

The result of a typeid operation is a reference to an object of a library type namedtype_info. Section 18.2.4 (p. 779) covers this type in more detail. To use the type_info class, the library headertypeinfo must be included.

typeid 操作符的結果是名爲 type_info 的標準庫類型的對象引用,第 18.2.4 節將更詳細地討論這個類型。要使用type_info 類,必須包含庫頭文件 typeinfo

Using the typeid Operator
使用 typeid 操作符

The most common use of typeid is to compare the types of two expressions or to compare the type of an expression to a specified type:

typeid 最常見的用途是比較兩個表達式的類型,或者將表達式的類型與特定類型相比較:

     Base *bp;
     Derived *dp;
     // compare type at run time of two objects
     if (typeid(*bp) == typeid(*dp)) {
         // bp and dp point to objects of the same type
     }
     // test whether run time type is a specific type
     if (typeid(*bp) == typeid(Derived)) {
         // bp actually points to a Derived
     }

In the first if, we compare the actual types of the objects to whichbp and dp point. If they both point to the same type, then the test succeeds. Similarly, the secondif succeeds if bp currently points to a Derived object.

第一個 if 中,比較 bp 所指對象與 dp 所指對象的實際類型,如果它們指向同一類型,則測試成功。類似地,如果bp 當前指向 Derived 對象,則第二個 if 成功。

Note that the operands to the typeid are expressions that are objectswe tested*bp, not bp:

注意,typeid 的操作數是表示對象的表達式——測試 *bp,而不是 bp

     // test always fails: The type of bp is pointer to Base
     if (typeid(bp) == typeid(Derived)) {
          // code never executed
     }

This test compares the type Base* to type Derived. These types are unequal, so this test will always failregardless of the type of the object to which bp points.

這個測試將 Base* 類型與 Derived 類型相比較,這兩個類型不相等,所以,無論bp 所指對象的類型是什麼,這個測試將問題失敗。

 

Dynamic type information is returned only if the operand to typeid is an object of a class type with virtual functions. Testing a pointer (as opposed to the object to which the pointer points) returns the static, compile-time type of the pointer.

只有當 typeid 的操作數是帶虛函數的類類型的對象的時候,才返回動態類型信息。測試指針(相對於指針指向的對象)返回指針的靜態的、編譯時類型。



If the value of a pointer p is 0, then typeid(*p) throws abad_typeid exception if the type of p is a type with virtual functions. If the type ofp does not define any virtuals, then the value of p is irrelevant. As when evaluating asizeof expression (Section 5.8, p. 167) the compiler does not evaluate *p. It uses the static type of p, which does not require that p itself be a valid pointer.

如果指針 p 的值是 0,那麼,如果 p 的類型是帶虛函數的類型,則 typeid(*p) 拋出一個bad_typeid 異常;如果 p 的類型沒有定義任何虛函數,則結果與 p 的值是不相關的。正像計算表達式sizeof第 5.8 節)一樣,編譯器不計算 *p,它使用 p 的靜態類型,這並不要求 p 本身是有效指針。


18.2.3. Using RTTI

18.2.3. RTTI 的使用

As an example of when RTTI might be useful, consider a class hierarchy for which we'd like to implement the equality operator. Two objects are equal if they have the same value for a given set of their data members. Each derived type may add its own data, which we will want to include when testing for equality.

作爲說明何時可以使用 RTTI 的例子,考慮一個類層次,我們希望爲它實現相等操作符。如果兩個對象的給定數據成員集合的值相同,它們就相等。每個派生類型可以增加自己的數據,我們希望在測試相等的時候包含這些數據。

Because the values considered in determining equality for a derived type might differ from those considered for the base type, we'll (potentially) need a different equality operator for each pair of types in the hierarchy. Moreover, we'd like to be able to use a given type as either the left-hand or right-hand operand, so we'll actually need two operators for each pair of types.

因爲確定派生類型的相等與確定基類類型的相等所考慮的值不同,所以對層次中的每一對類型(潛在地)需要一個不同的相等操作符。而且,希望能夠使用給類型作爲左操作數或右操作數,所以實際上對每一對類型將需要兩個操作符。

If our hierarchy has only two types, we need four functions:

如果類層次中只有兩個類型,就需要四個函數:

     bool operator==(const Base&, const Base&)
     bool operator==(const Derived&, const Derived&)
     bool operator==(const Derived&, const Base&);
     bool operator==(const Base&, const Derived&);

But if our hierarchy has several types, the number of operators we must define expands rapidlyfor only 3 types we'd need 9 operators. If the hierarchy has 4 types, we'd need 16, and so on.

但是,如果類層次中有幾個類型,必須定義的操作符的數目就迅速擴大——僅僅 3 個類型就需要 9 個操作符。如果類層次有 4 個類型,將需要 16 個操作符,以此類推。

We might think we could solve this problem by defining a set of virtual functions that would perform the equality test at each level in the hierarchy. Given those virtuals, we could define a single equality operator that operates on references to the base type. That operator could delegate its work to a virtual equal operation that would do the real work.

也許我們認爲可以通過定義一個虛函數集合來解決這個問題,這些虛函數可以在類層次中每一層執行相等測試。給定這些虛函數,可以定義單個相等操作符,操作基類類型的引用,該操作符可以將工作委派給可以完成實際工作的虛操作。

Unfortunately, virtual functions are not a good match to this problem. The trouble is deciding on the type for the parameter to theequal operation. Virtual functions must have the same parameter type(s) in both the base and derived classes. That implies that a virtualequal operation must have a parameter that is a reference to the base class.

但是,虛函數並不是解決這個問題的好辦法。麻煩在於決定 equal 操作的形參的類型。虛函數在基類類型和派生類型中必須有相同的形參類型,這意味着,虛equal 操作必須有一個形參是基類的引用。

However, when we compare two derived objects, we want to compare data members that might be particular to that derived class. If the parameter is a reference to base, we can use only members that are present in the base class. We cannot access members that are in the derived class but not in the base.

但是,當比較兩個派生類對象的時候,我們希望比較可能特定於派生類的數據成員。如果形參是基類的引用,就只能比較基類中出現的成員,我們不能訪問在派生類中但不在基類中出現的成員。

Thinking about the problem in this detail, we see that we want to return false if we attempt to compare objects of different types. Given this observation, we can now use RTTI to solve our problem.

仔細考慮這個問題,我們看到,希望在試圖比較不同類型的對象時返回假(false)。有了這個觀察,現在可以使用 RTTI 解決我們的問題。

We'll define a single equality operator. Each class will define a virtualequal function that first casts its operand to the right type. If the cast succeeds, then the real comparison will be performed. If the cast fails, then theequal operation will return false.

我們將定義單個相等操作符。每個類定義一個虛函數 equal,該函數首先將操作數強制轉換爲正確的類型。如果轉換成功,就進行真正的比較;如果轉換失敗,equal 操作就返回false

The Class Hierarchy
類層次

To make the concept a bit more concrete, let's assume that our classes look something like:

爲了使概念更清楚一點,假定類層次是這樣的:

     class Base {
         friend bool operator==(const Base&, const Base&);
     public:
         // interface members for Base
     protected:
         virtual bool equal(const Base&) const;
         // data and other implementation members of Base
     };
     class Derived: public Base {
         friend bool operator==(const Base&, const Base&);
     public:
         // other interface members for Derived
     private:
         bool equal(const Base&) const;
         // data and other implementation members of Derived
     };

A Type-Sensitive Equality Operator
類型敏感的相等操作符

Next let's look at how we might define the overall equality operator:

下面看看可以怎樣定義整體的相等操作符:

     bool operator==(const Base &lhs, const Base &rhs)
     {
        // returns false if typeids are different otherwise
        // returns lhs.equal(rhs)
        return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
     }

This operator returns false if the operands are different types. If they are the same type, then it delegates the real work of comparing the operands to the appropriate virtualequal function. If the operands are Base objects, then Base::equal will be called. If they areDerived objects, Derived::equal is called.

如果操作數類型不同,這個操作符就返回假;如果操作數類型相同,它就將實際比較操作數的工作委派給適當的虛函數 equal。如果操作數是Base 對象,就調用 Base::equal;如果操作數是 Derived 對象,就調用 Derived::equal

The Virtual equal Functions
虛函數 equal

Each class in the hierarchy must define its own version of equal. The functions in the derived classes will all start the same way: They'll cast their argument to the type of the class itself:

層次中的每個類都必須定義自己的 equal 版本。派生類中的 equal 函數將以相同的方式開始:它們將實參強制轉換爲類本身的類型。

     bool Derived::equal(const Base &rhs) const
     {
        if (const Derived *dp
                   = dynamic_cast<const Derived*>(&rhs)) {
           // do work to compare two Derived objects and return result
        } else
           return false;
     }

The cast should always succeedafter all, the function is called from the equality operator only after testing that the two operands are the same type. However, the cast is necessary so that the function can access the derived members of the right-hand operand. The operand is a Base&, so if we want to access members of theDerived, we must first do the cast.

這個強制轉換應該總是成功——畢竟,只有有測試了兩個操作數類型相同之後,才從相等操作符調用該函數。但是,這個強制轉換是必要的,以便函數可以訪問右操作數的派生類成員。因爲操作數是Base&,所以如果想要訪問 Derived 的成員,就必須首先進行強制轉換。

The Base-Class equal Function
基類 equal 函數

This operation is a bit simpler than the others:

這個操作比其他的簡單一點:

     bool Base::equal(const Base &rhs) const
     {
          // do whatever is required to compare to Base objects
     }

There is no need to cast the parameter before using it. Both *this and the parameter are Base objects, so all the operations available for this object are also defined for the parameter type.

使用形參之前不必強制轉換,*this 和形參都是 Base 對象,所以對形參類型也定義了該對象可用的所有操作。

18.2.4. The type_info Class

18.2.4. type_info

The exact definition of the type_info class varies by compiler, but the standard guarantees that all implementations will provide at least the operations listed inTable 18.2

type_info 類的確切定義隨編譯器而變化,但是,標準保證所有的實現將至少提供表 18.2 列出的操作。

Table 18.2. Operations on type_info
表 18.2. type_info 的操作

The class also provides a public virtual destructor, because it is intended to serve as a base class. If the compiler wants to provide additional type information, it should do so in a class derived fromtype_info.

因爲打算作基類使用,type_info 類也提供公用虛析構函數。如果編譯器想要提供附加的類型信息,應該在 type_info 的派生類中進行。

The default and copy constructors and the assignment operator are all defined asprivate, so we cannot define or copy objects of type type_info. The only way to createtype_info objects in a program is to use the typeid operator.

默認構造函數和複製構造函數以及賦值操作符都定義爲 private,所以不能定義或複製 type_info 類型的對象。程序中創建type_info 對象的唯一方法是使用 typeid 操作符。

The name function returns a C-style character string for the name of the type represented by thetype_info object. The value used for a given type depends on the compiler and in particular is not required to match the type names as used in a program. The only guarantee we have about the return fromname is that it returns a unique string for each type. Nonetheless, the name member can be used to print the name of a type_info object:

name 函數爲 type_info 對象所表示的類型的名字返回 C 風格字符串。給定類型所用的值取決於編譯器,具體來說,無須與程序中使用的類型名字匹配。對name 返回值的唯一保證是,它爲每個類型返回唯一的字符串。雖然如此,仍可以使用 name 成員來顯示 type_info 對象的名字:

     int iobj;
     cout << typeid(iobj).name() << endl
          << typeid(8.16).name() << endl
          << typeid(std::string).name() << endl
          << typeid(Base).name() << endl
          << typeid(Derived).name() << endl;

The format and value returned by name varies by compiler. This program, when executed on our machine, generates the following output:

name 返回的格式和值隨編譯器而變化。在我們的機器上執行時,這個程序產生下面的輸出:

     i
     d
     Ss
     4Base
     7Derived

 

The type_info class varies by compiler. Some compilers provide additional member functions that provide additional information about types used in a program. You should consult the reference manual for your compiler to understand the exact type_info support provided.

type_info 類隨編譯器而變。一些編譯器提供附加的成員函數,那些函數提供關於程序中所用類型的附加信息。你應該查閱編譯器的參考手冊來理解所提供的確切的type_info 支持。




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