源自《C++ primer》第四版 p419
包含指针的类需要特别注意复制控制,原因是复制指针时只复制指针中的地址,而不会复制指针指向的对象。
设计具有指针成员的类时,类设计者必须首先需要决定的是该指针应提供什么行为。将一个指针复制到另一个指针时,两个指针指向同一对象。当两个指针指向同一对象时,可能使用任一指针改变基础对象。类似地,很可能一个指针删除了一对象时,另一指针的用户还认为基础对象仍然存在。
指针成员默认具有与指针对象同样的行为。然而,通过不同的复制控制策略,可以为指针成员实现不同的行为。大多数 C++ 类采用以下三种方法之一管理指针成员:
1、指针成员采取常规指针型行为。这样的类具有指针的所有缺陷但无需特殊的复制控制。
2、类可以实现所谓的“智能指针”行为。指针所指向的对象是共享的,但类能够防止悬垂指针。
3、类采取值型行为。指针所指向的对象是唯一的,由每个类对象独立管理。
本节中介绍三个类,分别实现管理指针成员的三种不同方法。
一个带指针成员的简单类
为了阐明所涉及的问题,我们将实现一个简单类,该类包含一个 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;
- };
HasPtr 构造函数接受两个形参,将它们复制到 HasPtr 的数据成员。HasPtr 类提供简单的访问函数:函数 get_int 和 get_ptr 分别返回 int 成员和指针成员的值:set_int 和 set_ptr 成员则使我们能够改变这些成员,给 int 成员一个新值或使指针成员指向不同的对象。还定义了 get_ptr_val 和 set_ptr_val 成员,它们能够获取和设置指针所指向的基础值。
1、默认复制/赋值与指针成员
因为 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
复制之后,ptr1 和 ptr2 中的指针指向同一对象且两个对象中的 int 值相同。但是,因为指针的值不同于它所指对象的值,这两个成员的行为看来非常不同。复制之后,int 值是清楚和独立的,而指针则纠缠在一起。可能出现悬垂指针。
2、智能指针类
另一种方法是定义所谓的智能指针类。智能指针除了增加功能外,其行为像普通指针一样。本例中让智能指针负责删除共享对象。用户将动态分配一个对象并将该对象的地址传给新的 HasPtr 类。用户仍然可以通过普通指针访问对象,但绝不能删除指针。HasPtr 类将保证在撤销指向对象的最后一个 HasPtr 对象时删除对象。HasPtr 在其他方面的行为与普通指针一样。具体而言,复制对象时,副本和原对象将指向同一基础对象,如果通过一个副本改变基础对象,则通过另一对象访问的值也会改变。新的 HasPtr 类需要一个析构函数来删除指针,但是,析构函数不能无条件地删除指针。如果两个 HasPtr 对象指向同一基础对象,那么,在两个对象都撤销之前,我们并不希望删除基础对象。为了编写析构函数,需要知道这个 HasPtr 对象是否为指向给定对象的最后一个。
引入使用计数
定义智能指针的通用技术是采用一个使用计数。智能指针类将一个计数器与类指向的对象相关联。使用计数跟踪该类有多少个对象共享同一指针。使用计数为 0 时,删除对象。使用计数有时也称为引用计数。每次创建类的新对象时,初始化指针并将使用计数置为 1。当对象作为另一对象的副本而创建时,复制构造函数复制指针并增加与之相应的使用计数的值。对一个对象进行赋值时,赋值操作符减少左操作数所指对象的使用计数的值(如果使用计数减至 0,则删除对象),并增加右操作数所指对象的使用计数的值。最后,调用析构函数时,析构函数减少使用计数的值,如果计数减至 0,则删除基础对象。
- 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
使用计数类
- class U_Ptr {
- friend class HasPtr;
- int *ip;
- size_t use;
- U_Ptr(int *p): ip(p), use(1) { }
- ~U_Ptr() { delete ip; }
- };
图示如下:
如果复制这个对象,则如下图:
完整代码如下:
- //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; }
- };
- class HasPtr {
- public:
- // HasPtr owns the pointer; p must 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; }
- friend ostream& operator<<(ostream&, const HasPtr&);
- // 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;
- };
- 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;
- }
3、定义值类型
处理指针成员的另一个完全不同的方法,是给指针成员提供值语义:
- 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&);
- // wrong: don't define a destructor without also defining copy and assign
- ~HasPtr() { delete ptr; }
- friend ostream& operator<<(ostream&, const HasPtr&);
- // 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;
- };
- 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;
- }
转载于:https://blog.51cto.com/sourberry/515244