Team LiB
Previous Section Next Section

13.5. Managing Pointer Members

13.5. 管理指针成员

This book generally advocates the use of the standard library. One reason we do so is that using the standard library greatly reduces the need for pointers in modern C++ programs. However, many applications still require the use of pointers, particularly in the implementation of classes. Classes that contain pointers require careful attention to copy control. The reason they must do so is that copying a pointer copies only the address in the pointer. Copying a pointer does not copy the object to which the pointer points.

本书始终提倡使用标准库。这样做的一个原因是,使用标准库能够大大减少现代 C++ 程序中对指针的需要。然而,许多应用程序仍需要使用指针,特别是在类的实现中。包含指针的类需要特别注意复制控制,原因是复制指针时只复制指针中的地址,而不会复制指针指向的对象。

When designing a class with a pointer member, the first decision a class author must make is what behavior that pointer should provide. When we copy one pointer to another, the two pointers point to the same object. When two pointers point to the same object, it is possible to use either pointer to change the underlying object. Similarly, it is possible for one pointer to delete the object even though the user of the other pointer still thinks the underlying object exists.

设计具有指针成员的类时,类设计者必须首先需要决定的是该指针应提供什么行为。将一个指针复制到另一个指针时,两个指针指向同一对象。当两个指针指向同一对象时,可能使用任一指针改变基础对象。类似地,很可能一个指针删除了一对象时,另一指针的用户还认为基础对象仍然存在。

By default, a pointer member has the same behavior as a pointer object. However, through different copy-control strategies we can implement different behavior for pointer members. Most C++ classes take one of three approaches to managing pointer members:

指针成员默认具有与指针对象同样的行为。然而,通过不同的复制控制策略,可以为指针成员实现不同的行为。大多数 C++ 类采用以下三种方法之一管理指针成员:

  1. The pointer member can be given normal pointerlike behavior. Such classes will have all the pitfalls of pointers but will require no special copy control.

    指针成员采取常规指针型行为。这样的类具有指针的所有缺陷但无需特殊的复制控制。

  2. The class can implement so-called "smart pointer" behavior. The object to which the pointer points is shared, but the class prevents dangling pointers.

    类可以实现所谓的“智能指针”行为。指针所指向的对象是共享的,但类能够防止悬垂指针。

  3. The class can be given valuelike behavior. The object to which the pointer points will be unique to and managed separately by each class object.

    类采取值型行为。指针所指向的对象是唯一的,由每个类对象独立管理。

In this section we look at three classes that implement each of these different approaches to managing their pointer members.

本节中介绍三个类,分别实现管理指针成员的三种不同方法。

A Simple Class with a Pointer Member

一个带指针成员的简单类

To illustrate the issues involved, we'll implement a simple class that contains an int and a pointer:

为了阐明所涉及的问题,我们将实现一个简单类,该类包含一个 int 值和一个指针:

     // class that has a pointer member that behaves like a plain pointer
     class HasPtr {
     public:
         // copy of the values we're given
         HasPtr(int *p, int i): ptr(p), val(i) { }

         // const members to return the value of the indicated data member
         int *get_ptr() const { return ptr; }
         int get_int() const { return val; }

         // non const members to change the indicated data member
         void set_ptr(int *p) { ptr = p; }
         void set_int(int i) { val = i; }

         // return or change the value pointed to, so ok for const objects
         int get_ptr_val() const { return *ptr; }
         void set_ptr_val(int val) const { *ptr = val; }

     private:
         int *ptr;
         int val;
     };

The HasPtr constructor takes two parameters, which it copies into HasPtr's data members. The class provides simple accessor functions: The const functions get_int and get_ptr return the value of the int and pointer members, respectively; the set_int and set_ptr members let us change these members, giving a new value to the int or making the pointer point to a different object. We also define the get_ptr_val and set_ptr_val members. These members get and set the underlying value to which the pointer points.

HasPtr 构造函数接受两个形参,将它们复制到 HasPtr 的数据成员。HasPtr 类提供简单的访问函数:函数 get_intget_ptr 分别返回 int 成员和指针成员的值:set_intset_ptr 成员则使我们能够改变这些成员,给 int 成员一个新值或使指针成员指向不同的对象。还定义了 get_ptr_valset_ptr_val 成员,它们能够获取和设置指针所指向的基础值。

Default Copy/Assignment and Pointer Members

默认复制/赋值与指针成员

Because the class does not define a copy constructor, copying one HasPtr object to another copies both members:

因为 HasPtr 类没有定义复制构造函数,所以复制一个 HasPtr 对象将复制两个成员:

     int obj = 0;
     HasPtr ptr1(&obj, 42); // int* member points to obj, val is 42
     HasPtr ptr2(ptr1);     // int* member points to obj, val is 42

After the copy, the pointers in ptr1 and ptr1 both address the same object and the int values in each object are the same. However, the behavior of these two members appears quite different, because the value of a pointer is distinct from the value of the object to which it points. After the copy, the int values are distinct and independent, whereas the pointers are intertwined.

复制之后,ptr1ptr2 中的指针指向同一对象且两个对象中的 int 值相同。但是,因为指针的值不同于它所指对象的值,这两个成员的行为看来非常不同。复制之后,int 值是清楚和独立的,而指针则纠缠在一起。

Classes that have pointer members and use default synthesized copy control have all the pitfalls of ordinary pointers. In particular, the class itself has no way to avoid dangling pointers.

具有指针成员且使用默认合成复制构造函数的类具有普通指针的所有缺陷。尤其是,类本身无法避免悬垂指针。



Pointers Share the Same Object

指针共享同一对象

When we copy an arithmetic value, the copy is independent from the original. We can change one copy without changing the other:

复制一个算术值时,副本独立于原版,可以改变一个副本而不改变另一个:

     ptr1.set_int(0); // changes val member only in ptr1
     ptr2.get_int();  // returns 42
     ptr1.get_int();  // returns 0

When we copy a pointer, the address values are distinct, but the pointers point to the same underlying object. If we call set_ptr_val on either object, the underlying object is changed for both:

复制指针时,地址值是可区分的,但指针指向同一基础对象。如果在任一对象上调用 set_ptr_val,则二者的基础对象都会改变:

     ptr1.set_ptr_val(42); // sets object to which both ptr1 and ptr2 point
     ptr2.get_ptr_val();   // returns 42

When two pointers point to the same object, either one can change the value of the shared object.

两个指针指向同一对象时,其中任意一个都可以改变共享对象的值。

Dangling Pointers Are Possible

可能出现悬垂指针

Because our class copies the pointers directly, it presents our users with a potential problem: HasPtr stores the pointer it was given. It is up to the user to guarantee that the object to which that pointer points stays around as long as the HasPtr object does:

因为类直接复制指针,会使用户面临潜在的问题:HasPtr 保存着给定指针。用户必须保证只要 HasPtr 对象存在,该指针指向的对象就存在:

     int *ip = new int(42); // dynamically allocated int initialized to 42
     HasPtr ptr(ip, 10);    // Has Ptr points to same object as ip does
     delete ip;             // object pointed to by ip is freed
     ptr.set_ptr_val(0); // disaster: The object to which Has Ptr points was freed!

The problem here is that ip and the pointer inside ptr both point to the same object. When that object is deleted, the pointer inside HasPtr no longer points to a valid object. However, there is no way to know that the object is gone.

这里的问题是 ipptr 中的指针指向同一对象。删除了该对象时,ptr 中的指针不再指向有效对象。然而,没有办法得知对象已经不存在了。

Exercises Section 13.5

Exercise 13.20:

Given the original version of the HasPtr class that relies on the default definitions for copy-control, describe what happens in the following code:

对于 HasPtr 类的原始版本(依赖于复制控制的默认定义),描述下面代码中会发生什么:

     int i = 42;
     HasPtr p1(&i, 42);
     HasPtr p2 = p1;
     cout << p2.get_ptr_val() << endl;
     p1.set_ptr_val(0);
     cout << p2.get_ptr_val() << endl;

Exercise 13.21:

What would happen if we gave our HasPtr class a destructor that deleted its pointer member?

如果给 HasPtr 类添加一个析构函数,用来删除指针成员,会发生什么?


13.5.1. Defining Smart Pointer Classes

13.5.1. 定义智能指针类

In the previous section we defined a simple class that held a pointer and an int. The pointer member behaved in all ways like any other pointer. Any changes made to the object to which the pointer pointed were made to a single, shared object. If the user deleted that object, then our class had a dangling pointer. Its pointer member pointed at an object that no longer existed.

上节中我们定义了一个简单类,保存一个指针和一个 int 值。其中指针成员的行为与其他任意指针完全相同。对该指针指向的对象所做的任意改变都将作用于共享对象。如果用户删除该对象,则类就有一个悬垂指针,指向一个不复存在的对象。

An alternative to having a pointer member behave exactly like a pointer is to define what is sometimes referred to as a smart pointer class. A smart pointer behaves like an ordinary pointer except that it adds functionality. In this case, we'll give our smart pointer the responsibility for deleting the shared object. Users will dynamically allocate an object and pass the address of that object to our new HasPtr class. The user may still access the object through a plain pointer but must not delete the pointer. The HasPtr class will ensure that the object is deleted when the last HasPtr that points to it is destroyed.

除了使指针成员与指针完全相同之外,另一种方法是定义所谓的智能指针类。智能指针除了增加功能外,其行为像普通指针一样。本例中让智能指针负责删除共享对象。用户将动态分配一个对象并将该对象的地址传给新的 HasPtr 类。用户仍然可以通过普通指针访问对象,但绝不能删除指针。HasPtr 类将保证在撤销指向对象的最后一个 HasPtr 对象时删除对象。

In other ways, our HasPtr will behave like a plain pointer. In particular, when we copy a HasPtr object, the copy and the original will point to the same underlying object. If we change that object through one copy, the value will be changed when accessed through the other.

HasPtr 在其他方面的行为与普通指针一样。具体而言,复制对象时,副本和原对象将指向同一基础对象,如果通过一个副本改变基础对象,则通过另一对象访问的值也会改变。

Our new HasPtr class will need a destructor to delete the pointer. However, the destructor cannot delete the pointer unconditionally. If two HasPtr objects point to the same underlying object, we don't want to delete the object until both objects are destroyed. To write the destructor, we need to know whether this HasPtr is the last one pointing to a given object.

新的 HasPtr 类需要一个析构函数来删除指针,但是,析构函数不能无条件地删除指针。如果两个 HasPtr 对象指向同一基础对象,那么,在两个对象都撤销之前,我们并不希望删除基础对象。为了编写析构函数,需要知道这个 HasPtr 对象是否为指向给定对象的最后一个。

Introducing Use Counts
引入使用计数

A common technique used in defining smart pointers is to use a use count. The pointerlike class associates a counter with the object to which the class points. The use count keeps track of how many objects of the class share the same pointer. When the use count goes to zero, then the object is deleted. A use count is sometimes also referred to as a reference count.

定义智能指针的通用技术是采用一个使用计数。智能指针类将一个计数器与类指向的对象相关联。使用计数跟踪该类有多少个对象共享同一指针。使用计数为 0 时,删除对象。使用计数有时也称为引用计数

Each time a new object of the class is created, the pointer is initialized and the use count is set to 1. When an object is created as a copy of another, the copy constructor copies the pointer and increments the associated use count. When an object is assigned to, the assignment operator decrements the use count of the object to which the left-hand operand points (and deletes that object if the use count goes to zero) and increments the use count of the object pointed to by the right-hand operand. Finally, when the destructor is called, it decrements the use count and deletes the underlying object if the count goes to zero.

每次创建类的新对象时,初始化指针并将使用计数置为 1。当对象作为另一对象的副本而创建时,复制构造函数复制指针并增加与之相应的使用计数的值。对一个对象进行赋值时,赋值操作符减少左操作数所指对象的使用计数的值(如果使用计数减至 0,则删除对象),并增加右操作数所指对象的使用计数的值。最后,调用析构函数时,析构函数减少使用计数的值,如果计数减至 0,则删除基础对象。

The only wrinkle is deciding where to put the use count. The counter cannot go directly into our HasPtr object. To see why, consider what happens in the following case:

唯一的创新在于决定将使用计数放在哪里。计数器不能直接放在 HasPtr 对象中,为什么呢?考虑下面的情况:

     int obj;
     HasPtr p1(&obj, 42);
     HasPtr p2(p1);  // p1 and p2 both point to same int object
     HasPtr p3(p1);  // p1, p2, and p3 all point to same int object

If the use count is stored in a HasPtr object, how can we update it correctly when p3 is created? We could increment the count in p1 and copy that count into p3, but how would we update the counter in p2?

如果使用计数保存在 HasPtr 对象中,创建 p3 时怎样更新它?可以在 p1 中将计数增量并复制到 p3,但怎样更新 p2 中的计数?

The Use-Count Class
使用计数类

There are two classic strategies for implementing a use count, one of which we will use here; the other approach is described in Section 15.8.1 (p. 599). In the approach we use here, we'll define a separate concrete class to encapsulate the use count and the associated pointer:

实现使用计数有两种经典策略,在这里将使用其中一种,另一种方法在第 15.8.1 节中讲述。这里所用的方法中,需要定义一个单独的具体类用以封闭使用计数和相关指针:

     // private class for use by HasPtr only
     class U_Ptr {
         friend class HasPtr;
         int *ip;
         size_t use;
         U_Ptr(int *p): ip(p), use(1) { }
         ~U_Ptr() { delete ip; }
     };

All the members of this class are private. We don't intend ordinary users to use the U_Ptr class, so we do not give it any public members. The HasPtr class is made a friend so that its members will have access to the members of U_Ptr.

这个类的所有成员均为 private。我们不希望用户使用 U_Ptr 类,所以它没有任何 public 成员。将 HasPtr 类设置为友元,使其成员可以访问 U_Ptr 的成员。

The class is pretty simple, although the concept of how it works can be slippery. The U_Ptr class holds the pointer and the use count. Each HasPtr will point to a U_Ptr. The use count will keep track of how many HasPtr objects point to each U_Ptr object. The only functions U_Ptr defines are its constructor and destructor. The constructor copies the pointer, which the destructor deletes. The constructor also sets the use count to 1, indicating that a HasPtr object points to this U_Ptr.

尽管该类的工作原理比较难,但这个类相当简单。U_Ptr 类保存指针和使用计数,每个 HasPtr 对象将指向一个 U_Ptr 对象,使用计数将跟踪指向每个 U_Ptr 对象的 HasPtr 对象的数目。U_Ptr 定义的仅有函数是构造函数和析构函数,构造函数复制指针,而析构函数删除它。构造函数还将使用计数置为 1,表示一个 HasPtr 对象指向这个 U_Ptr 对象。

Assuming we just created a HasPtr object from a pointer that pointed to an int value of 42, we might picture the objects as follows:

假定刚从指向 int 值 42 的指针创建一个 HasPtr 对象,可以画出这些对象,如下图:

If we copy this object, then the objects will be as shown on the next page.

如果复制这个对象,则对象如下图所示。

Using the Use-Counted Class
使用计数类的使用

Our new HasPtr class holds a pointer to a U_Ptr, which in turn points to the actual underlying int object. Each member must be changed to reflect the fact that the class points to a U_Ptr rather than an int*.

新的 HasPtr 类保存一个指向 U_Ptr 对象的指针,U_Ptr 对象指向实际的 int 基础对象。必须改变每个成员以说明的 HasPtr 类指向一个 U_Ptr 对象而不是一个 int

We'll look first at the constructors and copy-control members:

先看看构造函数和复制控制成员:

     /* smart pointer class: takes ownership of the dynamically allocated
      *          object to which it is bound
      * User code must dynamically allocate an object to initialize a HasPtr
      * and must not delete that object; the HasPtr class will delete it
      */
     class HasPtr {
     public:
         // HasPtr owns the pointer; pmust have been dynamically allocated
         HasPtr(int *p, int i): ptr(new U_Ptr(p)), val(i) { }

         // copy members and increment the use count
         HasPtr(const HasPtr &orig):
            ptr(orig.ptr), val(orig.val) { ++ptr->use; }
         HasPtr& operator=(const HasPtr&);

         // if use count goes to zero, delete the U_Ptr object
         ~HasPtr() { if (--ptr->use == 0) delete ptr; }
     private:
         U_Ptr *ptr;        // points to use-counted U_Ptr class
         int val;
     };

The HasPtr constructor that takes a pointer and an int uses its pointer parameter to create a new U_Ptr object. After the HasPtr constructor completes, the HasPtr object points to a newly allocated U_Ptr object. That U_Ptr object stores the pointer we were given. The use count in that new U_Ptr is 1, indicating that only one HasPtr object points to it.

接受一个指针和一个 int 值的 HasPtr 构造函数使用其指针形参创建一个新的 U_Ptr 对象。HasPtr 构造函数执行完毕后,HasPtr 对象指向一个新分配的 U_Ptr 对象,该 U_Ptr 对象存储给定指针。新 U_Ptr 中的使用计数为 1,表示只有一个 HasPtr 对象指向它。

The copy constructor copies the members from its parameter and increments the use count. After the constructor completes, the newly created object points to the same U_Ptr object as the original and the use count of that U_Ptr object is incremented by one.

复制构造函数从形参复制成员并增加使用计数的值。复制构造函数执行完毕后,新创建对象与原有对象指向同一 U_Ptr 对象,该 U_Ptr 对象的使用计数加 1。

The destructor checks the use count in the underlying U_Ptr object. If the use count goes to 0, then this is the last HasPtr object that points to this U_Ptr. In this case, the HasPtr destructor deletes its U_Ptr pointer. Deleting that pointer has the effect of calling the U_Ptr destructor, which in turn deletes the underlying int object.

析构函数将检查 U_Ptr 基础对象的使用计数。如果使用计数为 0,则这是最后一个指向该 U_Ptr 对象的 HasPtr 对象,在这种情况下,HasPtr 析构函数删除其 U_Ptr 指针。删除该指针将引起对 U_Ptr 析构函数的调用,U_Ptr 析构函数删除 int 基础对象。

Assignment and Use Counts
赋值与使用计数

The assignment operator is a bit more complicated than the copy constructor:

赋值操作符比复制构造函数复杂一点:

     HasPtr& HasPtr::operator=(const HasPtr &rhs)
     {
         ++rhs.ptr->use;     // increment use count on rhs first
         if (--ptr->use == 0)
              delete ptr;    // if use count goes to 0 on this object, delete it
         ptr = rhs.ptr;      // copy the U_Ptr object
         val = rhs.val;      // copy the int member
         return *this;
     }

Here we start by incrementing the use count in the right-hand operand. Then we decrement and check the use count on this object. As with the destructor, if this is the last object pointing to the U_Ptr, we delete the object, which in turn destroys the underlying int. Having decremented (and possibly destroyed) the existing value in the left-hand operand, we then copy the pointer from rhs into this object. As usual, assignment returns a reference to this object.

在这里,首先将右操作数中的使用计数加 1,然后将左操作数对象的使用计数减 1 并检查这个使用计数。像析构函数中那样,如果这是指向 U_Ptr 对象的最后一个对象,就删除该对象,这会依次撤销 int 基础对象。将左操作数中的当前值减 1(可能撤销该对象)之后,再将指针从 rhs 复制到这个对象。赋值照常返回对这个对象的引用。

This assignment operator guards against self-assignment by incrementing the use count of rhs before decrementing the use count of the left-hand operand.

这个赋值操作符在减少左操作数的使用计数之前使 rhs 的使用计数加 1,从而防止自身赋值。



If the left and right operands are the same, the effect of this assignment operator will be to increment and then immediately decrement the use count in the underlying U_Ptr object.

如果左右操作数相同,赋值操作符的效果将是 U_Ptr 基础对象的使用计数加 1 之后立即减 1。

Changing Other Members
改变其他成员

The other members that access the int* now need to change to get to the int indirectly through the U_Ptr pointer:

现在需要改变访问 int* 的其他成员,以便通过 U_Ptr 指针间接获取 int

     class HasPtr {
     public:
         // copy control and constructors as before
         // accessors must change to fetch value from U_Ptr object
         int *get_ptr() const { return ptr->ip; }
         int get_int() const { return val; }

         // change the appropriate data member
         void set_ptr(int *p) { ptr->ip = p; }
         void set_int(int i) { val = i; }

         // return or change the value pointed to, so ok for const objects
         // Note: *ptr->ip is equivalent to *(ptr->ip)
         int get_ptr_val() const { return *ptr->ip; }
         void set_ptr_val(int i) { *ptr->ip = i; }
     private:
         U_Ptr *ptr;        // points to use-counted U_Ptr class
         int val;
     };

The functions that get and set the int member are unchanged. Those that operate on the pointer have to dereference the U_Ptr to get to the underlying int*.

获取和设置 int 成员的函数不变。那些使用指针操作的函数必须对 U_Ptr 解引用,以便获取 int* 基础对象。

When we copy HasPtr objects, the int member behaves the same as in our first class. Its value is copied; the members are independent. The pointer members in the copy and the original still point to the same underlying object. A change made to that object will affect the value as seen by either HasPtr object. However, users of HasPtr do not need to worry about dangling pointers. As long as they let the HasPtr class take care of freeing the object, the class will ensure that the object stays around as long as there are HasPtr objects that point to it.

复制 HasPtr 对象时,int 成员的行为与第一个类中一样。所复制的是 int 成员的值,各成员是独立的,副本和原对象中的指针仍指向同一基础对象,对基础对象的改变将影响通过任一 HasPtr 对象所看到的值。然而,HasPtr 的用户无须担心悬垂指针。只要他们让 HasPtr 类负责释放对象,HasPtr 类将保证只要有指向基础对象的 HasPtr 对象存在,基础对象就存在。

Advice: Managing Pointer Members

建议:管理指针成员

Objects with pointer members often need to define the copy-control members. If we rely on the synthesized versions, then the class puts a burden on its users. Users must ensure that the object to which the member points stays around for at least as long as the object that points to it does.

具有指针成员的对象一般需要定义复制控制成员。如果依赖合成版本,会给类的用户增加负担。用户必须保证成员所指向的对象存在,只要还有对象指向该对象。

To manage a class with pointer members, we must define all three copy-control members: the copy constructor, assignment operator, and the destructor. These members can define either pointerlike or valuelike behavior for the pointer member.

为了管理具有指针成员的类,必须定义三个复制控制成员:复制构造函数、赋值操作符和析构函数。这些成员可以定义指针成员的指针型行为或值型行为。

Valuelike classes give each object its own copy of the underlying values pointed to by pointer members. The copy constructor allocates a new element and copies the value from the object it is copying. The assignment operator destroys the existing object it holds and copies the value from its right-hand operand into its left-hand operand. The destructor destroys the object.

值型类将指针成员所指基础值的副本给每个对象。复制构造函数分配新元素并从被复制对象处复制值,赋值操作符撤销所保存的原对象并从右操作数向左操作数复制值,析构函数撤销对象。

As an alternative to defining either valuelike behavior or pointerlike behavior some classes are so-called "smart pointers." These classes share the same underlying value between objects, thus providing pointerlike behavior. But they use copy-control techniques to avoid some of the pitfalls of regular pointers. To implement smart pointer behavior, a class needs to ensure that the underlying object stays around until the last copy goes away. Use counting (Section 13.5.1, p. 495), is a common technique for managing smart pointer classes. Each copy of the same underlying value is given a use count. The copy constructor copies the pointer from the old object into the new one and increments the use count. The assignment operator decrements the use count of the left-hand operand and increments the count of the right-hand operand. If the use count of the left-hand operand goes to zero, the assignment operator must delete the object to which it points. Finally, the assignment operator copies the pointer from the right-hand operand into its left-hand operand. The destructor decrements the use count and deletes the underlying object if the count goes to zero.

作为定义值型行为或指针型行为的另一选择,是使用称为“智能指针”的一些类。这些类在对象间共享同一基础值,从而提供了指针型行为。但它们使用复制控制技术以避免常规指针的一些缺陷。为了实现智能指针行为,类需要保证基础对象一直存在,直到最后一个副本消失。使用计数(第 13.5.1 节)是管理智能指针类的通用技术。同一基础值的每个副本都有一个使用计数。复制构造函数将指针从旧对象复制到新对象时,会将使用计数加 1。赋值操作符将左操作数的使用计数减 1 并将右操作数的使用计数加 1,如果左操作数的使用计数减至 0,赋值操作符必须删除它所指向的对象,最后,赋值操作符将指针从右操作数复制到左操作数。析构函数将使用计数减 1,并且,如果使用计数减至 0,就删除基础对象。

These approaches to managing pointers occur so frequently that programmers who use classes with pointer members must be thoroughly familiar with these programming techniques.

管理指针的这些方法用得非常频繁,因此使用带指针成员类的程序员必须充分熟悉这些编程技术。




Exercises Section 13.5.1

Exercise 13.22:

What is a use count?

什么是使用计数?

Exercise 13.23:

What is a smart pointer? How does a smart pointer class differ from one that implements plain pointer behavior?

什么是智能指针?智能指针类如何与实现普通指针行为的类相区别?

Exercise 13.24:

Implement your own version of the use-counted HasPtr class.

实现你自己的使用计数的 HasPtr 类的版本。


13.5.2. Defining Valuelike Classes

13.5.2. 定义值型类

A completely different approach to the problem of managing pointer members is to give them value semantics. Simply put, classes with value semantics define objects that behave like the arithmetic types: When we copy a valuelike object, we get a new, distinct copy. Changes made to the copy are not reflected in the original, and vice versa. The string class is an example of a valuelike class.

处理指针成员的另一个完全不同的方法,是给指针成员提供值语义。具有值语义的类所定义的对象,其行为很像算术类型的对象:复制值型对象时,会得到一个不同的新副本。对副本所做的改变不会反映在原有对象上,反之亦然。string 类是值型类的一个例子。

To make our pointer member behave like a value, we must copy the object to which the pointer points whenever we copy the HasPtr object:

要使指针成员表现得像一个值,复制 HasPtr 对象时必须复制指针所指向的对象:

     /*
      * Valuelike behavior even though HasPtr has a pointer member:
      * Each time we copy a HasPtr object, we make a new copy of the
      * underlying int object to which ptr points.
      */
     class HasPtr {
     public:
         // no point to passing a pointer if we're going to copy it anyway
         // store pointer to a copy of the object we're given
         HasPtr(const int &p, int i): ptr(new int(p)), val(i) {}

         // copy members and increment the use count
         HasPtr(const HasPtr &orig):
            ptr(new int (*orig.ptr)), val(orig.val) { }

         HasPtr& operator=(const HasPtr&);
         ~HasPtr() { delete ptr; }
         // accessors must change to fetch value from Ptr object
         int get_ptr_val() const { return *ptr; }
         int get_int() const { return val; }

         // change the appropriate data member
         void set_ptr(int *p) { ptr = p; }
         void set_int(int i) { val = i; }

         // return or change the value pointed to, so ok for const objects
         int *get_ptr() const { return ptr; }
         void set_ptr_val(int p) const { *ptr = p; }
     private:
         int *ptr;        // points to an int
         int val;
     };

The copy constructor no longer copies the pointer. It now allocates a new int object and initializes that object to hold the same value as the object of which it is a copy. Each object always holds its own, distinct copy of its int value. Because each object holds its own copy, the destructor unconditionally deletes the pointer.

复制构造函数不再复制指针,它将分配一个新的 int 对象,并初始化该对象以保存与被复制对象相同的值。每个对象都保存属于自己的 int 值的不同副本。因为每个对象保存自己的副本,所以析构函数将无条件删除指针。

The assignment operator doesn't need to allocate a new object. It just has to remember to assign a new value to the object to which its int pointer points rather than assigning to the pointer itself:

赋值操作符不需要分配新对象,它只是必须记得给其指针所指向的对象赋新值,而不是给指针本身赋值:

     HasPtr& HasPtr::operator=(const HasPtr &rhs)
     {
         // Note: Every HasPtr is guaranteed to point at an actual int;
         //    We know that ptr cannot be a zero pointer
         *ptr = *rhs.ptr;       // copy the value pointed to
         val = rhs.val;         // copy the int
         return *this;
     }

In other words, we change the value pointed to but not the pointer.

换句话说,改变的是指针所指向的值,而不是指针。

As always, the assignment operator must be correct even if we're assigning an object to itself. In this case, the operations are inherently safe even if the left- and right-hand objects are the same. Thus, there is no need to explicitly check for self-assignment.

即使要将一个对象赋值给它本身,赋值操作符也必须总是保证正确。本例中,即使左右操作数相同,操作本质上也是安全的,因此,不需要显式检查自身赋值。



Exercises Section 13.5.2

Exercise 13.25:

What is a valuelike class?

什么是值型类?

Exercise 13.26:

Implement your own version of a valuelike HasPtr class.

实现你自己的值型 HasPtr 类版本?

Exercise 13.27:

The valuelike HasPtr class defines each of the copy-control members. Describe what would happen if the class defined

值型 HasPtr 类定义了所有复制控制成员。描述将会发生什么,如果该类:

  1. The copy constructor and destructor but no assignment operator.

    定义了复制构造函数和析构函数但没有定义赋值操作符。

  2. The copy constructor and assignment operator but no destructor.

    定义了复制构造函数和赋值操作符但没有定义析构函数。

  3. The destructor but neither the copy constructor nor assignment operator.

    定义了析构函数但没有定义复制构造函数和赋值操作符。

Exercise 13.28:

Given the following classes, implement a default constructor and the necessary copy-control members.

对于如下的类,实现默认构造函数和必要的复制控制成员。

     (a) class TreeNode {        (b) class BinStrTree {
         public:                         public:
             // ...                         //...
         private:                        private:
             std::string value;               TreeNode *root;
             int         count;          };
             TreeNode    *left;
             TreeNode    *right;
         };


Team LiB
Previous Section Next Section