笔记十一:智能指针(一)

前言:浅拷贝容易出现对同一内存空间进行2次撤销,造成程序崩溃。于是,我们可以利用指针智能来解决这一问题。本节主要介绍利用使用计数类构造智能指针类。


使用计数: 智能指针将一个计数器与类指向的对象相关联。使用计数跟踪该类有多少个对象共享同一指针。使用计数为0时,删除对象。

规则:
1、每次创建类的新对象时,初始化指针并将计数器置1
计数器置1的原因是在执行析构函数时,计数器先减1,然后再判断是否为0进行内存撤销,假设创建了一个新对象不进行任何操作直接进行析构,则减1载判断是否为0,那么计数器初始值就必须为1才能内存撤销。

2、当对象作为另一个对象的副本而创建时,复制构造函数复制指针并增加与之相应的使用计数的值
(对象增加,用于跟踪该类有多少个对象的引用计数当然要随之增加)

3、对一个对象进行赋值时,赋值操作符减少左操作所指对象的使用计数值(若使用计数减至为0,则删除对象),并增加右操作数所指对象的使用计数的值。
减少左操作数所指对象的使用计数,是因为左操作由原来指向a对象改为指向b对象,那么共享a的对象的数目自然要减少,同理b对象的共享对象的数目要相应增加。假设是同一对象的自我赋值操作,也满足上述原理。

例1:不同对象的赋值

int *p = new int(7);
    int *q = new int(8);
    SmartPtr<int> SP(p, 0);
    cout << "SP对象原始指向的对象:"<< SP.get_ptr_val() << endl;
    SmartPtr<int> SP1(q, 0);
    SP = SP1;
    cout << "SP = SP1后指向的对象:" << SP.get_ptr_val() << endl;

这里写图片描述

例2:同一对象的自我赋值
这里写图片描述

4、调用析构函数时,减少使用计数的值,若计数减至0,则删除基础对象
析构表明该类资源被释放,则指向基础对象的共享对象少了一个,使用计数当然减1了

实现方式一: 定义一个单独的使用计数类

template<class T> class SmartPtr;

//引用计数类
template<class T>
class U_Ptr
{
    friend class SmartPtr<T>;
    U_Ptr(T *p) : ip(p), use(1) { }
    ~U_Ptr() { delete ip;}
private:
    T   *ip;    //指向共享对象的指针
    size_t  use;    //引用计数

};

——类的所有成员均为private,是不希望普通用户可以使用U_Ptr类,将SmartPtr类设置为友元,是其可以访问 U_Ptr类。

——友元模板类的使用规则
方式一:在类之前声明模板类

template<class T> class B;

tempate<class T> A
{
    friend class B<T>;
};

方式二:在类中说明类为模板类

template<class T>
class A
{
    tempalte<class T> friend class B;
};

使用计数工作原理图:

这里写图片描述

这里写图片描述

1、U_Ptr类保存指针和使用计数,每个SmartPtr对象指向一个U_Ptr对象

2、使用计数跟踪指向每个U_Ptr对象的SmartPtr对象的数目。

智能指针类SmartPtr

/智能指针类
template<class T>
class SmartPtr
{
public:
    SmartPtr(T *p, T i) : ptr(new U_Ptr<T>(p)), val(i) { }
    SmartPtr(const SmartPtr &orig) : ptr(orig.ptr), val(orig.val) { ++ptr->use; }   //复制构造函数,使用计数加1
    SmartPtr<T>& operator=(const SmartPtr& rhs);
    ~SmartPtr() { 
        if (--ptr->use == 0)
            delete ptr;
    }
public:
    T get_ptr_val() const 
    { 
        int a = *ptr->ip;
        return *ptr->ip; 
    }
    void set_ptr_val(T i){ *ptr->ip = i; }
private:
    U_Ptr<T>    *ptr;   //指向U_ptr对象的指针
    T   val;
};

template<class T>
SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr& rhs)
{
    ++rhs.ptr->use;         //右操作数使用计数加1
    if(--ptr->use == 0)     //左操作数使用计数减1
    {
        delete ptr;
    }
    ptr = rhs.ptr;
    val = rhs.val;
    return *this;

}

—— ptr(new U_Ptr<T>(p)) 使用指针形参p创建一个新的U_Ptr对象,其中p为SmartPtr共享的基础对象指针

——SmartPtr(const SmartPtr &orig) 复制构造函数

——SmartPtr<T>& operator= 赋值操作符

——return *this; this为指向该类或对象的指针,故*this表示为该对象

——复制控制成员:复制构造函数、赋值操作符、析构函数

——三法则:如果一个类需要析构函数,则该类几乎也必然需要定义自己的复制构造函数和赋值操作符

——无论类是否定义了自己的析构函数,都会创建和运行合成析构函数。如果类定义了析构函数,则类定义的析构函数结束之后运行合成析构函数。

注意要点:

~U_Ptr() { delete ip;}

析构函数中,使用delete对ip进行销毁,表明ip指向一个动态分配的空间,而ip的初始化由ptr(new U_Ptr<T>(p)) 完成,表明p指向的内存空间必须是动态分配的。
1)p指向的内存空间是动态分配的,

int *p = new int(7);
    SmartPtr<int> SP(p, 0);
    cout << SP.get_ptr_val() << endl;

这里写图片描述

2、p指向的空间是静态分配的

int a = 7;
    int *p = &a;
    SmartPtr<int> SP(p, 0);
    cout << SP.get_ptr_val() << endl;

这里写图片描述

这里写图片描述

这是因为int a=7; 的内存空间是编译器在栈区中自动分配的,由编译器自动完成分配和回收操作,无需程序员delete操作。此时ip恰好指向a变量的内存地址,U_Ptr在析构时delete ip就会出现错误。

若修改U_Ptr的析构函数为~U_Ptr() { /*delete ip;*/} 则运行正常。

这里写图片描述

这里写图片描述

由图可知,ip与p存储的地址值相同,即共同指向基础对象。use = 2 表明有2个共享基础对象的SmartPtr对象,与程序中创建一个新对象SP,加上用SP复制的一个SP1对象的数目吻合。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值