Team LiB
Previous Section Next Section

15.4. Constructors and Copy Control

15.4. 构造函数和复制控制

The fact that each derived object consists of the (nonstatic) members defined in the derived class plus one or more base-class subobjects affects how derived-type objects are constructed, copied, assigned, and destroyed. When we construct, copy, assign, or destroy an object of derived type, we also construct, copy, assign, or destroy those base-class subobjects.

每个派生类对象由派生类中定义的(非 static)成员加上一个或多个基类子对象构成,这一事实影响着派生类型对象时,也会构造、复制、赋值和撤销这些基类子对象。

Constructors and the copy-control members are not inherited; each class defines its own constructor(s) and copy-control members. As is the case for any class, synthesized versions of the default constructor and the copy-control members will be used if the class does not define its own versions.

构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。像任何类一样,如果类不定义自己的默认构造函数和复制控制成员,就将使用合成版本。

15.4.1. Base-Class Constructors and Copy Control

15.4.1. 基类构造函数和复制控制

Constructors and copy control for base classes that are not themselves a derived class are largely unaffected by inheritance. Our Item_base constructor looks like many we've seen before:

本身不是派生类的基类,其构造函数和复制控制基本上不受继承影响。构造函数看起来像已经见过的许多构造函数一样:

     Item_base(const std::string &book = "",
               double sales_price = 0.0):
                      isbn(book), price(sales_price) { }

The only impact inheritance has on base-class constructors is that there is a new kind of user that must be considered when deciding which constructors to offer. Like any other member, constructors can be made protected or private. Some classes need special constructors that are intended only for their derived classes to use. Such constructors should be made protected.

继承对基类构造函数的唯一影响是,在确定提供哪些构造函数时,必须考虑一类新用户。像任意其他成员一样,构造函数可以为 protectedprivate,某些类需要只希望派生类使用的特殊构造函数,这样的构造函数应定义为 protected

15.4.2. Derived-Class Constructors

15.4.2. 派生类构造函数

Derived constructors are affected by the fact that they inherit from another class. Each derived constructor initializes its base class in addition to initializing its own data members.

派生类的构造函数受继承关系的影响,每个派生类构造函数除了初始化自己的数据成员之外,还要初始化基类。

The Synthesized Derived-Class Default Constructor
合成的派生类默认构造函数

A derived-class synthesized default constructor (Section 12.4.3, p. 458) differs from a nonderived constructor in only one way: In addition to initializing the data members of the derived class, it also initializes the base part of its object. The base part is initialized by the default constructor of the base class.

派生类的合成默认构造函数(第 12.4.3 节与非派生的构造函数只有一点不同:除了初始化派生类的数据成员之外,它还初始化派生类对象的基类部分。基类部分由基类的默认构造函数初始化。

For our Bulk_item class, the synthesized default constructor would execute as follows:

对于 Bulk_item 类,合成的默认构造函数会这样执行:

  1. Invoke the Item_base default constructor, which initializes the isbn member to the empty string and the price member to zero.

    调用 Item_base 的默认构造函数,将 isbn 成员初始化空串,将 price 成员初始化为 0。

  2. Initialize the members of Bulk_item using the normal variable initialization rules, which means that the qty and discount members would be uninitialized.

    用常规变量初始化规则初始化 Bulk_item 的成员,也就是说,qtydiscount 成员会是未初始化的。

Defining a Default Constructor
定义默认构造函数

Because Bulk_item has members of built-in type, we should define our own default constructor:

因为 Bulk_item 具有内置类型成员,所以应定义自己的默认构造函数:

     class Bulk_item : public Item_base {
     public:
         Bulk_item(): min_qty(0), discount(0.0) { }
         // as before
     };

This constructor uses the constructor initializer list (Section 7.7.3, p. 263) to initialize its min_qty and discount members. The constructor initializer also implicitly invokes the Item_base default constructor to initialize its base-class part.

这个构造函数使用构造函数初始化列表(第 7.7.3 节)初始化 min_qtydiscount 成员,该构造函数还隐式调用 Item_base 的默认构造函数初始化对象的基类部分。

The effect of running this constructor is that first the Item_base part is initialized using the Item_base default constructor. That constructor sets isbn to the empty string and price to zero. After the Item_base constructor finishes, the members of the Bulk_item part are initialized, and the (empty) body of the constructor is executed.

运行这个构造函数的效果是,首先使用 Item_base 的默认构造函数初始化 Item_base 部分,那个构造函数将 isbn 置为空串并将 price 置为 0。Item_base 的构造函数执行完毕后,再初始化 Bulk_item 部分的成员并执行构造函数的函数体(函数体为空)。

Passing Arguments to a Base-Class Constructor
向基类构造函数传递实参

In addition to the default constructor, our Item_base class lets users initialize the isbn and price members. We'd like to support the same initialization for Bulk_item objects. In fact, we'd like our users to be able to specify values for the entire Bulk_item, including the discount rate and quantity.

除了默认构造函数之外,Item_base 类还使用户能够初始化 isbnprice 成员,我们希望支持同样 Bulk_item 对象的初始化,事实上,我们希望用户能够指定整个 Bulk_item 的值,包括折扣率和数量。

The constructor initializer list for a derived-class constructor may initialize only the members of the derived class; it may not directly initialize its inherited members. Instead, a derived constructor indirectly initializes the members it inherits by including its base class in its constructor initializer list:

派生类构造函数的初始化列表只能初始化派生类的成员,不能直接初始化继承成员。相反派生类构造函数通过将基类包含在构造函数初始化列表中来间接初始化继承成员。

     class Bulk_item : public Item_base {
     public:
         Bulk_item(const std::string& book, double sales_price,
                   std::size_t qty = 0, double disc_rate = 0.0):
                      Item_base(book, sales_price),
                      min_qty(qty), discount(disc_rate) { }
         // as before
      };

This constructor uses the two-parameter Item_base constructor to initialize its base subobject. It passes its own book and sales_price arguments to that constructor. We might use this constructor as follows:

这个构造函数使用有两个形参 Item_base 构造函数初始化基类子对象,它将自己的 booksales_price 实参传递给该构造函数。这个构造函数可以这样使用:

     // arguments are the isbn, price, minimum quantity, and discount
     Bulk_item bulk("0-201-82470-1", 50, 5, .19);

bulk is built by first running the Item_base constructor, which initializes isbn and price from the arguments passed in the Bulk_item constructor initializer. After the Item_base constructor finishes, the members of Bulk_item are initialized. Finally, the (empty) body of the Bulk_item constructor is run.

要建立 bulk,首先运行 Item_base 构造函数,该构造函数使用从 Bulk_item 构造函数初始化列表传来的实参初始化 isbnpriceItem_base 构造函数执行完毕之后,再初始化 Bulk_item 的成员。最后,运行 Bulk_item 构造函数的(空)函数体。

The constructor initializer list supplies initial values for a class' base class and members. It does not specify the order in which those initializations are done. The base class is initialized first and then the members of the derived class are initialized in the order in which they are declared.

构造函数初始化列表为类的基类和成员提供初始值,它并不指定初始化的执行次序。首先初始化基类,然后根据声明次序初始化派生类的成员。



Using Default Arguments in a Derived Constructor
在派生类构造函数中使用默认实参

Of course, we might write these two Bulk_item constructors as a single constructor that takes default arguments:

当然,也可以将这两个 Bulk_item 构造函数编写为一个接受默认实参的构造函数:

     class Bulk_item : public Item_base {
     public:
         Bulk_item(const std::string& book, double sales_price,
                   std::size_t qty = 0, double disc_rate = 0.0):
                      Item_base(book, sales_price),
                      min_qty(qty), discount(disc_rate) { }
         // as before
      };

Here we provide defaults for each parameter so that the constructor might be used with zero to four arguments.

这里为每个形参提供了默认值,因此,可以用 0 至 4 个实参使用该构造函数。

Only an Immediate Base Class May Be Initialized
只能初始化直接基类

A class may initialize only its own immediate base class. An immediate base class is the class named in the derivation list. If class C is derived from class B, which is derived from class A, then B is the immediate base of C. Even though every C object contains an A part, the constructors for C may not initialize the A part directly. Instead, class C initializes B, and the constructor for class B in turn initializes A. The reason for this restriction is that the author of class B has specified how to construct and initialize objects of type B. As with any user of class B, the author of class C has no right to change that specification.

一个类只能初始化自己的直接基类。直接就是在派生列表中指定的类。如果类 C 从类 B 派生,类 B 从类 A 派生,则 BC 的直接基类。虽然每个 C 类对象包含一个 A 类部分,但 C 的构造函数不能直接初始化 A 部分。相反,需要类 C 初始化类 B,而类 B 的构造函数再初始化类 A。这一限制的原因是,类 B 的作者已经指定了怎样构造和初始化 B 类型的对象。像类 B 的任何用户一样,类 C 的作者无权改变这个规约。

As a more concrete example, our bookstore might have several discount strategies. In addition to a bulk discount, it might offer a discount for purchases up to a certain quantity and then charge the full price thereafter. Or it might offer a discount for purchases above a certain limit but not for purchases up to that limit.

作为更具体的例子,书店可以有几种折扣策略。除了批量折扣外,还可以为购买某个数量打折,此后按全价销售,或者,购买量超过一定限度的可以打折,在该限度之内不打折。

Each of these discount strategies is the same in that it requires a quantity and a discount amount. We might support these differing strategies by defining a new class named Disc_item to store the quantity and the discount amount. This class would not define a net_price function but would serve as a base class for classes such as Bulk_item that define the different discount strategies.

这些折扣策略都需要一个数量和一个折扣量,可以定义名为 Disc_item 的新类存储数量和折扣量,以支持这些不同的折扣策略。Disc_item 类可以不定义 net_price 函数,但可以作为定义不同折扣策略的其他类(如 Bulk_item 类)的基类。

Key Concept: Refactoring

关键概念:重构

Adding Disc_item to the Item_base hierarchy is an example of refactoring. Refactoring involves redesigning a class hierarchy to move operations and/or data from one class to another. Refactoring happens most often when classes are redesigned to add new functionality or handle other changes in that application's requirements.

Disc_item 加到 Item_base 层次是重构(refactoring)的一个例子。重构包括重新定义类层次,将操作和/或数据从一个类移到另一个类。为了适应应用程序的需要而重新设计类以便增加新函数或处理其他改变时,最有可能需要进行重构。

Refactoring is common in OO applications. It is noteworthy that even though we changed the inheritance hierarchy, code that uses the Bulk_item or Item_base classes would not need to change. However, when classes are refactored, or changed in any other way, any code that uses those classes must be recompiled.

重构常见在面向对象应用程序中非常常见。值得注意的是,虽然改变了继承层次,使用 Bulk_item 类或 Item_base 类的代码不需要改变。然而,对类进行重构,或以任意其他方式改变类,使用这些类的任意代码都必须重新编译。


To implement this design, we first need to define the Disc_item class:

要实现这个设计,首先需要定义 Disc_item 类:

     // class to hold discount rate and quantity
     // derived classes will implement pricing strategies using these data
     class Disc_item : public Item_base {
     public:
         Disc_item(const std::string& book = "",
                   double sales_price = 0.0,
                   std::size_t qty = 0, double disc_rate = 0.0):
                      Item_base(book, sales_price),
                      quantity(qty), discount(disc_rate) { }
         protected:
             std::size_t quantity; // purchase size for discount to apply
             double discount;      // fractional discount to apply
      };

This class inherits from Item_base and defines its own members, discount and quantity. Its only member function is the constructor, which initializes its Item_base base class and the members defined by Disc_item.

这个类继承 Item_base 类并定义了自己的 discountquantity 成员。它唯一的成员函数是构造函数,用以初始化基类和 Disc_item 定义的成员。

Next, we can reimplement Bulk_item to inherit from Disc_item, rather than inheriting directly from Item_base:

其次,可以重新实现 Bulk_item 以继承 Disc_item,而不再直接继承 Item_base

     // discount kicks in when a specified number of copies of same book are sold
     // the discount is expressed as a fraction to use to reduce the normal price
     class Bulk_item : public Disc_item {
     public:
         Bulk_item(const std::string& book = "",
                   double sales_price = 0.0,
                   std::size_t qty = 0, double disc_rate = 0.0):
              Disc_item(book, sales_price, qty, disc_rate) { }
         // redefines base version so as to implement bulk purchase discount policy
         double net_price(std::size_t) const;
     };

The Bulk_item class now has a direct base class, Disc_item, and an indirect base class, Item_base. Each Bulk_item object has three subobjects: an (empty) Bulk_item part and a Disc_item subobject, which in turn has an Item_base base subobject.

Bulk_item 类现在有一个直接基类 Disc_item,还有一个间接基类 Item_base。每个 Bulk_item 对象有三个子对象:一个(空的)Bulk_item 部分和一个 Disc_item 子对象,Disc_item 子对象又有一个 Item_base 基类子对象。

Even though Bulk_item has no data members of its own, it defines a constructor in order to obtain values to use to initialize its inherited members.

虽然 Bulk_item 没有自己的数据成员,但为获取值用来初始化其继承成员,它定义了一个构造函数。

A derived constructor may initialize only its immediate base class. Naming Item_base in the Bulk_item constructor initializer would be an error.

派生类构造函数只能初始化自己的直接基类,在 Bulk_item 类的构造函数初始化列表中指定 Item_base 是一个错误。

Key Concept: Respecting the Base-Class Interface

关键概念:尊重基类接口

The reason that a constructor can initialize only its immediate base class is that each class defines its own interface. When we define Disc_item, we specify how to initialize a Disc_item by defining its constructors. Once a class has defined its interface, all interactions with objects of that class should be through that interface, even when those objects are part of a derived object.

构造函数只能初始化其直接基类的原因是每个类都定义了自己的接口。定义 Disc_item 时,通过定义它的构造函数指定了怎样初始化 Disc_item 对象。一旦类定义了自己的接口,与该类对象的所有交互都应该通过该接口,即使对象是派生类对象的一部分也不例外。

For similar reasons, derived-class constructors may not initialize and should not assign to the members of its base class. When those members are public or protected, a derived constructor could assign values to its base class members inside the constructor body. However, doing so would violate the interface of the base. Derived classes should respect the initialization intent of their base classes by using constructors rather than assigning to these members in the body of the constructor.

同样,派生类构造函数不能初始化基类的成员且不应该对基类成员赋值。如果那些成员为 publicprotected,派生构造函数可以在构造函数函数体中给基类成员赋值,但是,这样做会违反基类的接口。派生类应通过使用基类构造函数尊重基类的初始化意图,而不是在派生类构造函数函数体中对这些成员赋值。


Exercises Section 15.4.2

Exercise 15.14:

Redefine the Bulk_item and Item_base classes so that they each need to define only a single constructor.

重新定义 Bulk_itemItem_base 类,使每个类只需定义一个构造函数。

Exercise 15.15:

Identify the base- and derived-class constructors for the library class hierarchy described in the first exercise on page 575.

对于第 15.2.5 节习题第一题中描述的图书馆类层次,识别基类和派生类构造函数。

Exercise 15.16:

Given the following base class definition,

对于下面的基类定义:

     struct Base {
         Base(int val): id(val) { }
     protected:
         int id;
     };

explain why each of the following constructors is illegal.

解释为什么下述每个构造函数是非法的。

     (a) struct C1 : public Base {
             C1(int val): id(val) { }
         };
     (b) struct C2 : public
             C1 { C2(int val): Base(val), C1(val){ }
         };
     (c) struct C3 : public
             C1 { C3(int val): Base(val) { }
         };
     (d) struct C4 : public Base {
             C4(int val) : Base(id + val){ }
         };
     (e) struct C5 : public Base {
             C5() { }
         };


15.4.3. Copy Control and Inheritance

15.4.3. 复制控制和继承

Like any other class, a derived class may use the synthesized copy-control members described in Chapter 13. The synthesized operations copy, assign, or destroy the base-class part of the object along with the members of the derived part. The base part is copied, assigned, or destroyed by using the base class' copy constructor, assignment operator, or destructor.

像任意其他类一样,派生类也可以使用第十三章所介绍的合成复制控制成员。合成操作对对象的基类部分连同派生部分的成员一起进行复制、赋值或撤销,使用基类的复制构造函数、赋值操作符或析构函数对基类部分进行复制、赋值或撤销。

Whether a class needs to define the copy-control members depends entirely on the class' own direct members. A base class might define its own copy control while the derived uses the synthesized versions or vice versa.

类是否需要定义复制控制成员完全取决于类自身的直接成员。基类可以定义自己的复制控制而派生类使用合成版本,反之亦然。

Classes that contain only data members of class type or built-in types other than pointers usually can use the synthesized operations. No special control is required to copy, assign, or destroy such members. Classes with pointer members often need to define their own copy control to manage these members.

只包含类类型或内置类型数据成员、不含指针的类一般可以使用合成操作,复制、赋值或撤销这样的成员不需要特殊控制。具有指针成员的类一般需要定义自己的复制控制来管理这些成员。

Our Item_base class and its derived classes can use the synthesized versions of the copy-control operations. When a Bulk_item is copied, the (synthesized) copy constructor for Item_base is invoked to copy the isbn and price members. The isbn member is copied by using the string copy constructor; the price member is copied directly. Once the base part is copied, then the derived part is copied. Both members of Bulk_item are doubles, and these members are copied directly. The assignment operator and destructor are handled similarly.

Item_base 类及其派生类可以使用复制控制操作的合成版本。复制 Bulk_item 对象时,调用(合成的)Item_base 复制构造函数复制 isbnprice 成员。使用 string 复制构造函数复制 isbn,直接复制 price 成员。一旦复制了基类部分,就复制派生部分。Bulk_item 的两个成员都是 double 型,直接复制这些成员。赋值操作符和析构函数类似处理。

Defining a Derived Copy Constructor
定义派生类复制构造函数

If a derived class explicitly defines its own copy constructor or assignment operator, that definition completely overrides the defaults. The copy constructor and assignment operator for inherited classes are responsible for copying or assigning their base-class components as well as the members in the class itself.

如果派生类显式定义自己的复制构造函数或赋值操作符,则该定义将完全覆盖默认定义。被继承类的复制构造函数和赋值操作符负责对基类成分以及类自己的成员进行复制或赋值。



If a derived class defines its own copy constructor, that copy constructor usually should explicitly use the base-class copy constructor to initialize the base part of the object:

如果派生类定义了自己的复制构造函数,该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分:

     class Base { /* ... */ };
     class Derived: public Base {
     public:
         // Base::Base(const Base&) not invoked automatically
         Derived(const Derived& d):
              Base(d) /* other member initialization */ { /*... */ }
     };

The initializer Base(d) converts (Section 15.3, p. 577) the derived object, d, to a reference to its base part and invokes the base-class copy constructor. Had the initializer for the base class been omitted,

初始化函数 Base(d) 将派生类对象 d 转换(第 15.3 节)为它的基类部分的引用,并调用基类复制构造函数。如果省略基类初始化函数,如下代码:

     // probably incorrect definition of the Derived copy constructor
     Derived(const Derived& d) /* derived member initizations */
                                   {/* ... */ }

the effect would be to run the Base default constructor to initialize the base part of the object. Assuming that the initialization of the Derived members copied the corresponding elements from d, then the newly constructed object would be oddly configured: Its Base part would hold default values, while its Derived members would be copies of another object.

效果是运行 Base 的默认构造函数初始化对象的基类部分。假定 Derived 成员的初始化从 d 复制对应成员,则新构造的对象将具有奇怪的配置:它的 Base 部分将保存默认值,而它的 Derived 成员是另一对象的副本。

Derived-Class Assignment Operator
派生类赋值操作符

As usual, the assignment operator is similar to the copy constructor: If the derived class defines its own assignment operator, then that operator must assign the base part explicitly:

赋值操作符通常与复制构造函数类似:如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显式赋值。

     // Base::operator=(const Base&) not invoked automatically
     Derived &Derived::operator=(const Derived &rhs)
     {
        if (this != &rhs) {
            Base::operator=(rhs); // assigns the base part
            // do whatever needed to clean up the old value in the derived part
            // assign the members from the derived
        }
        return *this;
     }

The assignment operator must, as always, guard against self-assignment. Assuming the left- and right-hand operands differ, then we call the Base class assignment operator to assign the base-class portion. That operator might be defined by the class or it might be the synthesized assignment operator. It doesn't matterwe can call it directly. The base-class operator will free the old value in the base part of the left-hand operand and will assign the new values from rhs. Once that operator finishes, we continue doing whatever is needed to assign the members in the derived class.

赋值操作符必须防止自身赋值。假定左右操作数不同,则调用 Base 类的赋值操作符给基类部分赋值。该操作符可以由类定义,也可以是合成赋值操作符,这没什么关系——我们可以直接调用它。基类操作符将释放左操作数中基类部分的值,并赋以来自 rhs 的新值。该操作符执行完毕后,接着要做的是为派生类中的成员赋值。

Derived-Class Destructor
派生类析构函数

The destructor works differently from the copy constructor and assignment operator: The derived destructor is never responsible for destroying the members of its base objects. The compiler always implicitly invokes the destructor for the base part of a derived object. Each destructor does only what is necessary to clean up its own members:

析构函数的工作与复制构造函数和赋值操作符不同:派生类析构函数不负责撤销基类对象的成员。编译器总是显式调用派生类对象基类部分的析构函数。每个析构函数只负责清除自己的成员:

     class Derived: public Base {
     public:
         // Base::~Base invoked automatically
         ~Derived()    { /* do what it takes to clean up derived members */ }
      };

Objects are destroyed in the opposite order from which they are constructed: The derived destructor is run first, and then the base-class destructors are invoked, walking back up the inheritance hierarchy.

对象的撤销顺序与构造顺序相反:首先运行派生析构函数,然后按继承层次依次向上调用各基类析构函数。

15.4.4. Virtual Destructors

15.4.4. 虚析构函数

The fact that destructors for the base parts are invoked automatically has an important consequence for the design of base classes.

自动调用基类部分的析构函数对基类的设计有重要影响。

When we delete a pointer that points to a dynamically allocated object, the destructor is run to clean up the object before the memory for that object is freed. When dealing with objects in an inheritance hierarchy, it is possible that the static type of the pointer might differ from the dynamic type of the object that is being deleted. We might delete a pointer to the base type that actually points to a derived object.

删除指向动态分配对象的指针时,需要运行析构函数在释放对象的内存之前清除对象。处理继承层次中的对象时,指针的静态类型可能与被删除对象的动态类型不同,可能会删除实际指向派生类对象的基类类型指针。

If we delete a pointer to base, then the base-class destructor is run and the members of the base are cleaned up. If the object really is a derived type, then the behavior is undefined. To ensure that the proper destructor is run, the destructor must be virtual in the base class:

如果删除基类指针,则需要运行基类析构函数并清除基类的成员,如果对象实际是派生类型的,则没有定义该行为。要保证运行适当的析构函数,基类中的析构函数必须为虚函数:

     class Item_base {
     public:
         // no work, but virtual destructor needed
         // if base pointer that points to a derived object is ever deleted
         virtual ~Item_base() { }
     };

If the destructor is virtual, then when it is invoked through a pointer, which destructor is run will vary depending on the type of the object to which the pointer points:

如果析构函数为虚函数,那么通过指针调用时,运行哪个析构函数将因指针所指对象类型的不同而不同:

     Item_base *itemP = new Item_base; // same static and dynamic type
     delete itemP;          // ok: destructor for Item_base called
     itemP = new Bulk_item; // ok: static and dynamic types differ
     delete itemP;          // ok: destructor for Bulk_item called

Like other virtual functions, the virtual nature of the destructor is inherited. Therefore, if the destructor in the root class of the hierarchy is virtual, then the derived destructors will be virtual as well. A derived destructor will be virtual whether the class explicitly defines its destructor or uses the synthesized destructor.

像其他虚函数一样,析构函数的虚函数性质都将继承。因此,如果层次中根类的析构函数为虚函数,则派生类析构函数也将是虚函数,无论派生类显式定义析构函数还是使用合成析构函数,派生类析构函数都是虚函数。

Destructors for base classes are an important exception to the Rule of Three (Section 13.3, p. 485). That rule says that if a class needs a destructor, then the class almost surely needs the other copy-control members. A base class almost always needs a destructor so that it can make the destructor virtual. If a base class has an empty destructor in order to make it virtual, then the fact that the class has a destructor is not an indication that the assignment operator or copy constructor is also needed.

基类析构函数是三法则(第 13.3 节)的一个重要例外。三法则指出,如果类需要析构函数,则类几乎也确实需要其他复制控制成员。基类几乎总是需要构造函数,从而可以将析构函数设为虚函数。如果基类为了将析构函数设为虚函数则具有空析构函数,那么,类具有析构函数并不表示也需要赋值操作符或复制构造函数。

The root class of an inheritance hierarchy should define a virtual destructor even if the destructor has no work to do.

即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。



Constructors and Assignment Are Not Virtual
构造函数和赋值操作符不是虚函数

Of the copy-control members, only the destructor should be defined as virtual. Constructors cannot be defined as virtual. Constructors are run before the object is fully constructed. While the constructor is running, the object's dynamic type is not complete.

在复制控制成员中,只有析构函数应定义为虚函数,构造函数不能定义为虚函数。构造函数是在对象完全构造之前运行的,在构造函数运行的时候,对象的动态类型还不完整。

Although we can define a virtual operator= member function in the base class, doing so does not affect the assignment operators used in the derived classes. Each class has its own assignment operator. The assignment operator in a derived class has a parameter that has the same type as the class itself. That type must differ from the parameter type for the assignment operator in any other class in the hierarchy.

虽然可以在基类中将成员函数 operator= 定义为虚函数,但这样做并不影响派生类中使用的赋值操作符。每个类有自己的赋值操作符,派生类中的赋值操作符有一个与类本身类型相同的形参,该类型必须不同于继承层次中任意其他类的赋值操作符的形参类型。

Making the assignment operator virtual is likely to be confusing because a virtual function must have the same parameter type in base and derived classes. The base-class assignment operator has a parameter that is a reference to its own class type. If that operator is virtual, then each class gets a virtual member that defines an operator= that takes a base object. But this operator is not the same as the assignment operator for the derived class.

将赋值操作符设为虚函数可能会令人混淆,因为虚函数必须在基类和派生类中具有同样的形参。基类赋值操作符有一个形参是自身类类型的引用,如果该操作符为虚函数,则每个类都将得到一个虚函数成员,该成员定义了参数为一个基类对象的 operator=。但是,对派生类而言,这个操作符与赋值操作符是不同的。

Making the class assignment operator virtual is likely to be confusing and unlikely to be useful.

将类的赋值操作符设为虚函数很可能会令人混淆,而且不会有什么用处。



Exercises Section 15.4.4

Exercise 15.17:

Describe the conditions under which a class should have a virtual destructor.

说明在什么情况下类应该具有虚析构函数。

Exercise 15.18:

What operations must a virtual destructor perform?

虚析构函数必须执行什么操作?

Exercise 15.19:

What if anything is likely to be incorrect about this class definition?

如果这个类定义有错,可能是什么错?



     class AbstractObject {
     public:
         virtual void doit();
          // other members not including any of the copy-control
 functions
     };

Exercise 15.20:

Recalling the exercise from Section 13.3 (p. 487) in which you wrote a class whose copy-control members printed a message, add print statements to the constructors of the Item_base and Bulk_item classes. Define the copy-control members to do the same job as the synthesized versions but that also print a message. Now write programs using objects and functions that use the Item_base types. In each case, predict what objects will be created and destroyed and compare your predictions with what your programs generate. Continue experimenting until you can correctly predict which copy-control members are executed for a given bit of code.

回忆在第 13.3 节习题中编写的类,该类的复制控制成员打印一条消息,为 Item_baseBulk_item 类的构造函数增加打印语句。定义复制控制成员,使之完成与合成版本相同的工作外,还打印一条消息。应用使用了 Item_base 类型的那些对象和函数编写一些程序,在每种情况下,预测将会创建和撤销什么对象,并将你的预测与程序所产生的结果进行比较。继续实验,直至你能够正确地预测对于给定的代码片段,会执行哪些复制控制成员。


15.4.5. Virtuals in Constructors and Destructors

15.4.5. 构造函数和析构函数中的虚函数

A derived object is constructed by first running a base-class constructor to initialize the base part of the object. While the base-class constructor is executing, the derived part of the object is uninitialized. In effect, the object is not yet a derived object.

构造派生类对象时首先运行基类构造函数初始化对象的基类部分。在执行基类构造函数时,对象的派生类部分是未初始化的。实际上,此时对象还不是一个派生类对象。

When a derived object is destroyed, its derived part is destroyed first, and then its base parts are destroyed in the reverse order of how they were constructed.

撤销派生类对象时,首先撤销它的派生类部分,然后按照与构造顺序的逆序撤销它的基类部分。

In both cases, while a constructor or destructor is running, the object is incomplete. To accommodate this incompleteness, the compiler treats the object as if its type changes during construction or destruction. Inside a base-class constructor or destructor, a derived object is treated as if it were an object of the base type.

在这两种情况下,运行构造函数或析构函数的时候,对象都是不完整的。为了适应这种不完整,编译器将对象的类型视为在构造或析构期间发生了变化。在基类构造函数或析构函数中,将派生类对象当作基类类型对象对待。

The type of an object during construction and destruction affects the binding of virtual functions.

构造或析构期间的对象类型对虚函数的绑定有影响。

If a virtual is called from inside a constructor or destructor, then the version that is run is the one defined for the type of the constructor or destructor itself.

如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。



This binding applies to a virtual whether the virtual is called directly by the constructor (or destructor) or is called indirectly from a function that the constructor (or destructor) called.

无论由构造函数(或析构函数)直接调用虚函数,或者从构造函数(或析构函数)所调用的函数间接调用虚函数,都应用这种绑定。

To understand this behavior, consider what would happen if the derived-class version of a virtual function were called from a base-class constructor (or destructor). The derived version of the virtual probably accesses members of the derived object. After all, if the derived-class version didn't need to use members from the derived object, the derived class could probably use the definition from the base class. However, the members of the derived part of the object aren't initialized while the base constructor (or destructor) is running. In practice, if such access were allowed, the program would probably crash.

要理解这种行为,考虑如果从基类构造函数(或析构函数)调用虚函数的派生类版本会怎么样。虚函数的派生类版本很可能会访问派生类对象的成员,毕竟,如果派生类版本不需要使用派生类对象的成员,派生类多半能够使用基类中的定义。但是,对象的派生部分的成员不会在基类构造函数运行期间初始化,实际上,如果允许这样的访问,程序很可能会崩溃。

Team LiB
Previous Section Next Section