智能指针类HasPtr

   智能指针是存储指向动态分配对象指针的类,用于控制对象的生存期,能够确保自动正确的销毁动态分配的对象,防止内存泄露。HasPtr 在其他方面的行为与普通指针一样。具体而言,复制对象时,副本和原对象将指向同一基础对象,如果通过一个副本改变基础对象,则通过另一对象访问的值也会改变。

   用智能指针的原因:智能指针主要设计目的是避免悬垂指针。 HasPtr 智能指针类需要一个析构函数来删除指针,但是,它的析构函数不能无条件地删除指针。如果两个 HasPtr 对象指向同一基础对象,那么,在两个对象都撤销之前, 我们并不希望删除基础对象。 为了编写析构函数, 需要知道这个HasPtr对象是否为指向给定对象的最后一个。
引入“使用计数”

    定义智能指针的通用技术是采用一个 “使用计数”。智能指针类将一个计数器类指向的对象相关联。使用计数器跟踪该类有多少个对象共享同一指针(应该是“指向同一对象”吧?)。使用计数为 0 时,删除基础对象。

    每次创建类的新对象时,初始化指针并将使用计数置为 1。以下是该类需要定义的三个复制控制函数:

   (1) 当对象作为另一对象的副本而创建时,复制构造函数复制指针并增加与之相应的使用计数的值

   (2) 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的使用计数的值(如果使用计数减至 0,则删除对象),并增加右操作数所指对象的使用计数的值。(右操作数是一个指针,该指针指向的对象又多了一个指针(左操作数)指着。)

   (3)最后,调用析构函数时,析构函数减少使用计数的值,如果计数减至 0,则删除基础对象

该计数器不能直接放在 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

如果使用计数保存在 HasPtr 对象中, 创建 p3 时怎样更新它?可以p1通过调用复制构造函数复制到 p3,并使p3中的计数加1,但又怎样更新p2 中的计数?(p2还是在p1的计数基础上加1)

这里所用的方法中,需要定义一个单独的具体类用以封闭使用计数和相关指针:
class U_Ptr {
friend class HasPtr;
int *ip;
size_t use;
U_Ptr(int *p): ip(p), use(1) { }    //ip指向int型基础变量
~U_Ptr() { delete ip; }             //析构函数中删除基础变量
};
    这个类的所有成员均为 private。我们不希望用户使用 U_Ptr 类,所以它没有任何 public 成员。将HasPtr 类设置为友元, 使其成员可以访问 U_Ptr 的成员。
    U_Ptr 类保存指针和使用计数,每个 HasPtr 对象将指向一个 U_Ptr 对象,使用计数将跟踪指向每个U_Ptr 对象的 HasPtr 对象的数目。 U_Ptr 定义的仅有函数是构造函数和析构函数,构造函数复制指针,而析构函数删除它。构造函数还将使用计数置为 1,表示一个 HasPtr 对象指向这个 U_Ptr 对象。

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

先看看构造函数和复制控制成员:
class HasPtr {
public:  
HasPtr(int *p, int i): ptr(new U_Ptr(p)), val(i)  { }

HasPtr(const HasPtr &orig):ptr(orig.ptr), val(orig.val)  { ++ptr->use; }

HasPtr& operator=(const HasPtr&);

~HasPtr() { if (--ptr->use == 0) delete ptr; }

private:
U_Ptr *ptr;    // points to use-counted U_Ptr class
int val;
};
接受一个指针和一个 int 值的 HasPtr 构造函数使用其指针形参创建一个新的 U_Ptr 对象。HasPtr 构造函数执行完毕后,HasPtr 对象指向一个新分配的 U_Ptr 对象,该 U_Ptr 对象存储给定指针。新 U_Ptr 中的使用计数为 1,表示只有一个 HasPtr 对象指向它。复制构造函数从形参复制成员并增加使用计数的值。复制构造函数执行完毕后,新创建对象与原有对象指向同一 U_Ptr 对象,该 U_Ptr 对象的使用计数加1。析构函数将检查 U_Ptr 基础对象的使用计数。如果使用计数为 0,则这是
最后一个指向该 U_Ptr 对象的 HasPtr 对象,在这种情况下,HasPtr 析构函数删除其 U_Ptr 指针。删除该指针将引起对 U_Ptr 析构函数的调用,U_Ptr 析构函数删除 int 基础对象。

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

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
++rhs.ptr->use; // 右操作数对应的计数加1
if (--ptr->use == 0)//左操作数对应的计数减1;
delete ptr;
ptr = rhs.ptr; // 复制 U_Ptr对象
val = rhs.val; //复制int成员变量
return *this;
}
在这里,首先将右操作数中的使用计数加 1,然后将左操作数对象的使用计数减 1 并检查这个使用计数。像析构函数中那样,如果这是指向 U_Ptr 对象的最后一个对象,就删除该对象,这会依次撤销 int 基础对象。将左操作数中的当前值减 1(可能撤销该对象)之后,再将指针从 rhs 复制到这个对象。赋值照常返回对这个对象的引用。这个赋值操作符在减少左操作数的使用计数之前使 rhs 的使用计数加 1,从而防止自身赋值。如果左右操作数相同,赋值操作符的效果将是 U_Ptr 基础对象的使用计数加 1 之后立即减 1。

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

int get_int() const { return val; } //获取指向的值

void set_int(int i) { val = i; }         //改变指向的值

int *get_ptr() const { return ptr->ip; }  //获取指针值

void set_ptr(int *p) { ptr->ip = p; }  //改变指针值


int get_ptr_val() const { return *ptr->ip; }   //获得指针指向的值

void set_ptr_val(int i) { *ptr->ip = i; }        //设置指针指向的值

private:

U_Ptr *ptr;
int val;
};
获取和设置 int 成员的函数不变。那些使用指针操作的函数必须对 U_Ptr解引用,以便获取 int* 基础对象。复制 HasPtr 对象时,int 成员的行为与第一个类中一样。所复制的是 int成员的值,各成员是独立的,副本和原对象中的指针仍指向同一基础对象,对基础对象的改变将影响通过任一 HasPtr 对象所看到的值。然而,HasPtr 的用户无须担心悬垂指针。只要他们让 HasPtr 类负责释放对象,HasPtr 类将保证只要有指向基础对象的 HasPtr 对象存在,基础对象就存在。

猜你喜欢

转载自blog.csdn.net/u011306659/article/details/22169247