15.5. Class Scope under Inheritance15.5. 继承情况下的类作用域Each class maintains its own scope (Section 12.3, p. 444) within which the names of its members are defined. Under inheritance, the scope of the derived class is nested within the scope of its base classes. If a name is unresolved within the scope of the derived class, the enclosing base-class scope(s) are searched for a definition of that name. 每个类都保持着自己的作用域(第 12.3 节),在该作用域中定义了成员的名字。在继承情况下,派生类的作用域嵌套在基类作用域中。如果不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义。 It is this hierarchical nesting of class scopes under inheritance that allows the members of the base class to be accessed directly as if they are members of the derived class. When we write 正是这种类作用域的层次嵌套使我们能够直接访问基类的成员,就好象这些成员是派生类成员一样。如果编写如下代码: Bulk_item bulk; cout << bulk.book(); the use of the name book is resolved as follows: 名字 book 的使用将这样确定:
15.5.1. Name Lookup Happens at Compile Time15.5.1. 名字查找在编译时发生The static type of an object, reference, or pointer determines the actions that the object can perform. Even when the static and dynamic types might differ, as can happen when a reference or pointer to a base type is used, the static type determines what members can be used. As an example, we might add a member to the Disc_item class that returns a pair holding the minimum (or maximum) quantity and the discounted price: 对象、引用或指针的静态类型决定了对象能够完成的行为。甚至当静态类型和动态类型可能不同的时候,就像使用基类类型的引用或指针时可能会发生的,静态类型仍然决定着可以使用什么成员。例如,可以给 Disc_item 类增加一个成员,该成员返回一个保存最小(或最大)数量和折扣价格的 pair 对象:
class Disc_item : public Item_base {
public:
std::pair<size_t, double> discount_policy() const
{ return std::make_pair(quantity, discount); }
// other members as before
};
We can access discount_policy only through an object, pointer, or reference of type Disc_item or a class derived from Disc_item: 只能通过 Disc_item 类型或 Disc_item 派生类型的对象、指针或引用访问 discount_policy: Bulk_item bulk; Bulk_item *bulkP = &bulk; // ok: static and dynamic types are the same Item_base *itemP = &bulk; // ok: static and dynamic types differ bulkP->discount_policy(); // ok: bulkP has type Bulk_item* itemP->discount_policy(); // error: itemP has type Item_base* The call through itemP is an error because a pointer (reference or object) to a base type can access only the base parts of an object and there is no discount_policy member defined in the base class. 重新定义 itemP 的访问是错误的,因为基类类型的指针(引用或对象)只能访问对象的基类部分,而在基类中没有定义 discount_policy 成员。
15.5.2. Name Collisions and Inheritance15.5.2. 名字冲突与继承Although a base-class member can be accessed directly as if it were a member of the derived class, the member retains its base-class membership. Normally we do not care which actual class contains the member. We usually need to care only when a base- and derived-class member share the same name. 虽然可以直接访问基类成员,就像它是派生类成员一样,但是成员保留了它的基类成员资格。一般我们并不关心是哪个实际类包含成员,通常只在基类和派生类共享同一名字时才需要注意。
struct Base { Base(): mem(0) { } protected: int mem; }; struct Derived : Base { Derived(int i): mem(i) { } // initializes Derived::mem int get_mem() { return mem; } // returns Derived::mem protected: int mem; // hides mem in the base }; The reference to mem inside get_mem is resolved to the name inside Derived. Were we to write get_mem 中对 mem 的引用被确定为使用 Derived 中的名字。如果编写如下代码:
Derived d(42);
cout << d.get_mem() << endl; // prints 42
then the output would be 42. 则输出将是 42。 Using the Scope Operator to Access Hidden Members使用作用域操作符访问被屏蔽成员We can access a hidden base-class member by using the scope operator: 可以使用作用域操作符访问被屏蔽的基类成员: struct Derived : Base { int get_base_mem() { return Base::mem; } }; The scope operator directs the compiler to look for mem starting in Base. 作用域操作符指示编译器在 Base 中查找 mem。
15.5.3. Scope and Member Functions15.5.3. 作用域与成员函数A member function with the same name in the base and derived class behaves the same way as a data member: The derived-class member hides the base-class member within the scope of the derived class. The base member is hidden, even if the prototypes of the functions differ: 在基类和派生类中使用同一名字的成员函数,其行为与数据成员一样:在派生类作用域中派生类成员将屏蔽基类成员。即使函数原型不同,基类成员也会被屏蔽: struct Base { int memfcn(); }; struct Derived : Base { int memfcn(int); // hides memfcn in the base }; Derived d; Base b; b.memfcn(); // calls Base::memfcn d.memfcn(10); // calls Derived::memfcn d.memfcn(); // error: memfcn with no arguments is hidden d.Base::memfcn(); // ok: calls Base::memfcn The declaration of memfcn in Derived hides the declaration in Base. Not surprisingly, the first call through b, which is aBase object, calls the version in the base class. Similarly, the second call through d calls the one from Derived. What can be surprising is the third call: Derived 中的 memfcn 声明隐藏了 Base 中的声明。这并不奇怪,第一个调用通过 Base 对象 b 调用基类中的版本,同样,第二个调用通过 d 调用 Derived 中的版本。可能比较奇怪的是第三个调用: d.memfcn(); // error: Derived has no memfcn that takes no arguments To resolve this call, the compiler looks for the name memfcn, which it finds in the class Derived. Once the name is found, the compiler looks no further. This call does not match the definition of memfcn in Derived, which expects an int argument. The call provides no such argument and so is in error. 要确定这个调用,编译器需要查找名字 memfcn,并在 Derived 类中找到。一旦找到了名字,编译器就不再继续查找了。这个调用与 Derived 中的 memfcn 定义不匹配,该定义希望接受 int 实参,而这个函数调用没有提供那样的实参,因此出错。
Overloaded Functions重载函数As with any other function, a member function (virtual or otherwise) can be over-loaded. A derived class can redefine zero or more of the versions it inherits. 像其他任意函数一样,成员函数(无论虚还是非虚)也可以重载。派生类可以重定义所继承的 0 个或多个版本。
If a derived class wants to make all the overloaded versions available through its type, then it must either redefine all of them or none of them. 如果派生类想通过自身类型使用的重载版本,则派生类必须要么重定义所有重载版本,要么一个也不重定义。 Sometimes a class needs to redefine the behavior of only some of the versions in an overloaded set, and wants to inherit the meaning for others. It would be tedious in such cases to have to redefine every base-class version in order to redefine the ones that the class needs to specialize. 有时类需要仅仅重定义一个重载集中某些版本的行为,并且想要继承其他版本的含义,在这种情况下,为了重定义需要特化的某个版本而不得不重定义每一个基类版本,可能会令人厌烦。 Instead of redefining every base-class version that it inherits, a derived class can provide a using declaration (Section 15.2.5, p. 574) for the overloaded member. A using declaration specifies only a name; it may not specify a parameter list. Thus, a using declaration for a base-class member function name adds all the overloaded instances of that function to the scope of the derived-class. Having brought all the names into its scope, the derived class need redefine only those functions that it truly must define for its type. It can use the inherited definitions for the others. 派生类不用重定义所继承的每一个基类版本,它可以为重载成员提供 using 声明(第 15.2.5 节)。一个 using 声明只能指定一个名字,不能指定形参表,因此,为基类成员函数名称而作的 using 声明将该函数的所有重载实例加到派生类的作用域。将所有名字加入作用域之后,派生类只需要重定义本类型确实必须定义的那些函数,对其他版本可以使用继承的定义。 15.5.4. Virtual Functions and Scope15.5.4. 虚函数与作用域Recall that to obtain dynamic binding, we must call a virtual member through a reference or a pointer to a base class. When we do so, the compiler looks for the function in the base class. Assuming the name is found, the compiler checks that the arguments match the parameters. 还记得吗,要获得动态绑定,必须通过基类的引用或指针调用虚成员。当我们这样做时,编译器器将在基类中查找函数。假定找到了名字,编译器就检查实参是否与形参匹配。 We can now understand why virtual functions must have the same prototype in the base and derived classes. If the base member took different arguments than the derived-class member, there would be no way to call the derived function from a reference or pointer to the base type. Consider the following (artificial) collection of classes: 现在可以理解虚函数为什么必须在基类和派生类中拥有同一原型了。如果基类成员与派生类成员接受的实参不同,就没有办法通过基类类型的引用或指针调用派生类函数。考虑如下(人为的)为集合: class Base { public: virtual int fcn(); }; class D1 : public Base { public: // hides fcn in the base; this fcn is not virtual int fcn(int); // parameter list differs from fcn in Base // D1 inherits definition of Base::fcn() }; class D2 : public D1 { public: int fcn(int); // nonvirtual function hides D1::fcn(int) int fcn(); // redefines virtual fcn from Base }; The version of fcn in D1 does not redefine the virtual fcn from Base. Instead, it hides fcn from the base. Effectively, D1 has two functions named fcn: The class inherits a virtual named fcn from the Base and defines its own, nonvirtual member named fcn that takes an int parameter. However, the virtual from the Base cannot be called from a D1 object (or reference or pointer to D1) because that function is hidden by the definition of fcn(int). D1 中的 fcn 版本没有重定义 Base 的虚函数 fcn,相反,它屏蔽了基类的 fcn。结果 D1 有两个名为 fcn 的函数:类从 Base 继承了一个名为 fcn 的虚函数,类又定义了自己的名为 fcn 的非虚成员函数,该函数接受一个 int 形参。但是,从 Base 继承的虚函数不能通过 D1 对象(或 D1 的引用或指针)调用,因为该函数被 fcn(int) 的定义屏蔽了。 The class D2 redefines both functions that it inherits. It redefines the virtual version of fcn originally defined in Base and the nonvirtual defined in D1. 类 D2 重定义了它继承的两个函数,它重定义了 Base 中定义的 fcn 的原始版本并重定义了 D1 中定义的非虚版本。 Calling a Hidden Virtual through the Base Class通过基类调用被屏蔽的虚函数When we call a function through a base-type reference or pointer, the compiler looks for that function in the base class and ignores the derived classes: 通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类: Base bobj; D1 d1obj; D2 d2obj; Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj; bp1->fcn(); // ok: virtual call, will call Base::fcnat run time bp2->fcn(); // ok: virtual call, will call Base::fcnat run time bp3->fcn(); // ok: virtual call, will call D2::fcnat run time All three pointers are pointers to the base type, so all three calls are resolved by looking in Base to see if fcn is defined. It is, so the calls are legal. Next, because fcn is virtual, the compiler generates code to make the call at run time based on the actual type of the object to which the reference or pointer is bound. In the case of bp2, the underlying object is a D1. That class did not redefine the virtual version of fcn that takes no arguments. The call through bp2 is made (at run time) to the version defined in Base. 三个指针都是基类类型的指针,因此通过在 Base 中查找 fcn 来确定这三个调用,所以这些调用是合法的。另外,因为 fcn 是虚函数,所以编译器会生成代码,在运行时基于引用指针所绑定的对象的实际类型进行调用。在 bp2 的情况,基本对象是 D1 类的,D1 类没有重定义不接受实参的虚函数版本,通过 bp2 的函数调用(在运行时)调用 Base 中定义的版本。
![]() |
Exercises Section 15.5.4
|