15.3. Conversions and Inheritance15.3. 转换与继承
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 Conversions15.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 类的复制构造函数或赋值操作符时,将发生下列步骤:
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. 在这种情况下,我们说 bulk 的 Bulk_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. 像继承的成员函数一样,从派生类到基类的转换可能是也可能不是可访问的。转换是否访问取决于在派生类的派生列表中指定的访问标号。
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 继承,则用户代码和后代类都可以使用派生类到基类的转换。如果类是使用 private 或protected 继承派生的,则用户代码不能将派生类型对象转换为基类对象。如果是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 Derived15.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 Identification18.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:
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 提供了可选的机制。然而,这种机制比使用虚函数更容易出错:程序员必须知道应该将对象强制转换为哪种类型,并且必须检查转换是否成功执行了。
18.2.1. The dynamic_cast Operator18.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 中的条件失败。
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_cast 因 basePtr 引用了 Base 对象而失败,则else 子句进行适应于 Base 的处理来代替。在 if 条件内部进行检查的另一好处是,不可能在 dynamic_cast 和测试转换结果之间插入代码,因此,不可能在测试转换是否成功之前不经意地使用 derivedPtr。第三个好处是,在if 外部不能访问该指针,如果转换失败,则在后面的忘了测试的地方,未绑定的指针是不可用的。
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 Operator18.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 所指对象的类型是什么,这个测试将问题失败。
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 RTTI18.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虚函数 equalEach 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 Class18.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
|