Team LiB
Previous Section Next Section

13.3. The Destructor

13.3. 析构函数

One purpose of a constructor is to provide for the automatic acquisition of a resource. For example, a constructor might allocate a buffer or open a file. Having allocated the resource in the constructor, we need a corresponding operation that automatically deallocates or otherwise releases the resource. The destructor is a special member function that can be used to do whatever resource deallocation is needed. It serves as the complement to the constructors of the class.

构造函数的一个用途是自动获取资源。例如,构造函数可以分配一个缓冲区或打开一个文件,在构造函数中分配了资源之后,需要一个对应操作自动回收或释放资源。析构函数就是这样的一个特殊函数,它可以完成所需的资源回收,作为类构造函数的补充。

When a Destructor Is Called

何时调用析构函数

The destructor is called automatically whenever an object of its class is destroyed:

撤销类对象时会自动调用析构函数:

     // p points to default constructed object
     Sales_item *p = new Sales_item;
     {
                               // new scope
         Sales_item item(*p);  // copy constructor copies *p into item
         delete p;             // destructor called on object pointed to by p
     }                         // exit local scope; destructor called on item

Variables such as item are destroyed automatically when they go out of scope. Hence, the destructor on item is run when the close curly is encountered.

变量(如 item)在超出作用域时应该自动撤销。因此,当遇到右花括号时,将运行 item 的析构函数。

An object that is dynamically allocated is destroyed only when a pointer pointing to the object is delete d. If we do not delete a pointer to a dynamically allocated object, then the destructor is never run on that object. The object will persist forever, leading to a memory leak. Moreover, any resources used inside the object will also not be released.

动态分配的对象只有在指向该对象的指针被删除时才撤销。如果没有删除指向动态对象的指针,则不会运行该对象的析构函数,对象就一直存在,从而导致内存泄漏,而且,对象内部使用的任何资源也不会释放。

The destructor is not run when a reference or a pointer to an object goes out of scope. The destructor is run only when a pointer to a dynamically allocated object is deleted or when an actual object (not a reference to the object) goes out of scope.

当对象的引用或指针超出作用域时,不会运行析构函数。只有删除指向动态分配对象的指针或实际对象(而不是对象的引用)超出作用域时,才会运行析构函数。



Destructors are also run on the elements of class type in a containerwhether a library container or built-in arraywhen the container is destroyed:

撤销一个容器(不管是标准库容器还是内置数组)时,也会运行容器中的类类型元素的析构函数:

     {
         Sales_item *p = new Sales_item[10]; // dynamically allocated
         vector<Sales_item> vec(p, p + 10);  // local object
         // ...
         delete [] p; // array is freed; destructor run on each element
      }   // vec goes out of scope; destructor run on each element

The elements in the container are always destroyed in reverse order: The element indexed by size() - 1 is destroyed first, followed by the one indexed by size() - 2 and so on until element [0], which is destroyed last.

容器中的元素总是按逆序撤销:首先撤销下标为 size() - 1 的元素,然后是下标为 size() - 2 的元素……直到最后撤销下标为 [0] 的元素。

When to Write an Explicit Destructor

何时编写显式析构函数

Many classes do not require an explicit destructor. In particular, a class that has a constructor does not necessarily need to define its own destructor. Destructors are needed only if there is work for them to do. Ordinarily they are used to relinquish resources acquired in the constructor or during the lifetime of the object.

许多类不需要显式析构函数,尤其是具有构造函数的类不一定需要定义自己的析构函数。仅在有些工作需要析构函数完成时,才需要析构函数。析构函数通常用于释放在构造函数或在对象生命期内获取的资源。

A useful rule of thumb is that if a class needs a destructor, it will also need the assignment operator and a copy constructor. This rule is often referred to as the Rule of Three, indicating that if you need a destructor, then you need all three copy-control members.

如果类需要析构函数,则它也需要赋值操作符和复制构造函数,这是一个有用的经验法则。这个规则常称为三法则,指的是如果需要析构函数,则需要所有这三个复制控制成员。



A destructor is not limited only to relinquishing resources. A destructor, in general, can perform any operation that the class designer wishes to have executed subsequent to the last use of an object of that class.

析构函数并不仅限于用来释放资源。一般而言,析构函数可以执行任意操作,该操作是类设计者希望在该类对象的使用完毕之后执行的。

The Synthesized Destructor

合成析构函数

Unlike the copy constructor or assignment operator, the compiler always synthesizes a destructor for us. The synthesized destructor destroys each nonstatic member in the reverse order from that in which the object was created. In consequence, it destroys the members in reverse order from which they are declared in the class. For each member that is of class type, the synthesized destructor invokes that member's destructor to destroy the object.

与复制构造函数或赋值操作符不同,编译器总是会为我们合成一个析构函数。合成析构函数按对象创建时的逆序撤销每个非 static 成员,因此,它按成员在类中声明次序的逆序撤销成员。对于类类型的每个成员,合成析构函数调用该成员的析构函数来撤销对象。

Destroying a member of built-in or compound type has no effect. In particular, the synthesized destructor does not delete the object pointed to by a pointer member.

撤销内置类型成员或复合类型的成员没什么影响。尤其是,合成析构函数并不删除指针成员所指向的对象。



How to Write a Destructor

如何编写析构函数

Our Sales_item class is an example of a class that allocates no resources and so does not need its own destructor. Classes that do allocate resources usually need to define a destructor to free those resources. The destructor is a member function with the name of the class prefixed by a tilde (~). It has no return value and takes no parameters. Because it cannot specify any parameters, it cannot be overloaded. Although we can define multiple class constructors, we can provide only a single destructor to be applied to all objects of our class.

Sales_item 类是类没有分配资源因此不需要自己的析构函数的一个例子。分配了资源的类一般需要定义析构函数以释放那些资源。析构函数是个成员函数,它的名字是在类名字之前加上一个代字号(~),它没有返回值,没有形参。因为不能指定任何形参,所以不能重载析构函数。虽然可以为一个类定义多个构造函数,但只能提供一个析构函数,应用于类的所有对象。

An important difference between the destructor and the copy constructor or assignment operator is that even if we write our own destructor, the synthesized destructor is still run. For example, we might write the following empty destructor for class Sales_item:

析构函数与复制构造函数或赋值操作符之间的一个重要区别是,即使我们编写了自己的析构函数,合成析构函数仍然运行。例如,可以为 Sales_item: 类编写如下的空析构函数:

     class Sales_item {
     public:
        // empty; no work to do other than destroying the members,
        // which happens automatically
         ~Sales_item() { }
        // other members as before
     };

When objects of type Sales_item are destroyed, this destructor, which does nothing, would be run. After it completes, the synthesized destructor would also be run to destroy the members of the class. The synthesized destructor destroys the string member by calling the string destructor, which frees the memory used to hold the isbn. The units_sold and revenue members are of built-in type, so the synthesized destructor does nothing to destroy them.

撤销 Sales_item 类型的对象时,将运行这个什么也不做的析构函数,它执行完毕后,将运行合成析构函数以撤销类的成员。合成析构函数调用 string 析构函数来撤销 string 成员,string 析构函数释放了保存 isbn 的内存。units_soldrevenue 成员是内置类型,所以合成析构函数撤销它们不需要做什么。

Exercises Section 13.3

Exercise 13.11:

What is a destructor? What does the synthesized destructor do? When is a destructor synthesized? When must a class define its own destructor?

什么是析构函数?合成析构函数有什么用?什么时候会合成析构函数?什么时候一个类必须定义自己的析构函数?

Exercise 13.12:

Determine whether the NoName class skteched in the exercises on page 481, is likely to need a destructor. If so, implement it.

确定在第 13.1.2 节习题 13.4 中概略定义的 NoName 类是否需要析构函数,如果需要,实现它。

Exercise 13.13:

Determine whether the Employee class, defined in the exercises on page 484, needs a destructor. If so, implement it.

确定在第 13.2 节习题 13.10 中定义的 Employee 类是否需要析构函数,如果需要,实现它。

Exercise 13.14:

A good way to understand copy-control members and constructors is to define a simple class with these members in which each member prints its name:

理解复制控制成员和构造函数的一个良好方式是定义一个简单类,该类具有这些成员,每个成员打印自己的名字:

     struct Exmpl {
         Exmpl() { std::cout << "Exmpl()" << std::endl; }
         Exmpl(const Exmpl&)
           { std::cout << "Exmpl(const Exmpl&)" << std::endl; }
     // ...
     };

Write a class like Exmpl, giving it the copy-control members and other constructors. Now write a program using objects of type Exmpl in various ways: pass them as non-reference and reference parameters; dynamically allocate them; put them in containers, and so forth. Studying which constructors and copy-control members are executed and when can be helpful in cementing your understanding of these concepts.

编写一个像 Exmpl 这样的类,给出复制控制成员和其他构造函数。然后写一个程序,用不同方式使用 Exmpl 类型的对象:作为非引用形参和引用形参传递,动态分配,放在容器中,等等。研究何时执行哪个构造函数和复制构造函数,可以帮助你融会贯通地理解这些概念。

Exercise 13.15:

How many destructor calls occur in the following code fragment?

下面的代码段中发生了多少次析构函数的调用?

     void fcn(const Sales_item *trans, Sales_item accum)
     {
         Sales_item item1(*trans), item2(accum);
         if (!item1.same_isbn(item2)) return;
         if (item1.avg_price() <= 99) return;
         else if (item2.avg_price() <= 99) return;
         // ...
     }


Team LiB
Previous Section Next Section