C++智能指针简单实现

参考:
C++ 引用计数技术及智能指针的简单实现 - melonstreet - 博客园
c++ primer 5th 12.1

智能指针是什么

简单来说,智能指针是一个管理内存的类,它对普通指针进行封装,使智能指针类对象具有普通指针类型一样的操作。具体而言,拷贝对象时,副本和原对象都指向同一存储区域,如果通过一个副本改变其所指的值,则通过另一对象访问的值也会改变。所不同的是,智能指针能够对内存进行进行自动管理,避免出现悬垂指针等情况。

普通指针存在的问题

多个指针指向同一个基础对象时,如果某个指针delete了该基础对象,对这个指针来说它是明确了它所指的对象被释放掉了,所以它不会再对所指对象进行操作,但是对于剩下的其他指针来说呢?它们还傻傻地指向已经被删除的基础对象并随时准备对它进行操作。于是悬垂指针就形成了,程序崩溃也“指日可待”。我们通过代码+图来来探求悬垂指针的解决方法。

#include <iostream>
using namespace std;

int main(){
    int *ptr1 = new int(1);
    int *ptr2 = ptr1;
    int *ptr3 = ptr2;
    cout << *ptr1 << endl;//1
    cout << *ptr2 << endl;//1
    cout << *ptr3 << endl;//1
    delete ptr1;
    cout << *ptr2 << endl;//0
    return 0;
}

运行结果是输出ptr2时并不是期待的1,因为1已经被删除了。这个过程是这样的:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从图可以看出,错误的产生来自于ptr1的”无知“:它并不知道还有其他指针共享着它指向的对象。如果有个办法让ptr1知道,除了它自己外还有两个指针指向基础对象,而它不应该删除基础对象,那么悬垂指针的问题就得以解决了。如下图:
在这里插入图片描述
在这里插入图片描述
那么何时才可以删除基础对象呢?当然是只有一个指针指向基础对象的时候,这时通过该指针就可以大大方方地把基础对象删除了。

什么是引用计数?

如何来让指针知道还有其他指针的存在呢?这个时候我们该引入引用计数的概念了。引用计数是这样一个技巧,它允许有多个相同值的对象共享这个值的实现。引用计数的使用常有两个目的:

  • 简化跟踪堆中(也即C++中new出来的)的对象的过程。一旦一个对象通过调用new被分配出来,记录谁拥有这个对象是很重要的,因为其所有者要负责对它进行delete。但是对象所有者可以有多个,且所有权能够被传递,这就使得内存跟踪变得困难。引用计数可以跟踪对象所有权,并能够自动销毁对象。可以说引用计数是个简单的垃圾回收体系。
  • 节省内存,提高程序运行效率。如何很多对象有相同的值,为这多个相同的值存储多个副本是很浪费空间的,所以最好做法是让左右对象都共享同一个值的实现。C++标准库中string类采取一种称为”写时复制“的技术,使得只有当字符串被修改的时候才创建各自的拷贝,否则可能(标准库允许使用但没强制要求)采用引用计数技术来管理共享对象的多个对象。这不是本文的讨论范围。

智能指针实现

智能指针的实现策略有两种:辅助类与句柄类。这里介绍辅助类的实现方法。

基础对象类

首先,我们来定义一个基础对象类Point类,为了方便后面我们验证智能指针是否有效,我们为Point类创建如下接口:

class Point{
public:
	Point(int xVal = 0, int yVal = 0) :x(xVal), y(yVal) { }
	int getX() const { return x; }
	int getY() const { return y; }
	void setX(int xVal) { x = xVal; }
	void setY(int yVal) { y = yVal; }
private:
	int x, y;
};

辅助类

在创建智能指针类之前,我们先创建一个辅助类。这个类的所有成员皆为私有类型,因为它不被普通用户所使用。为了只为智能指针使用,还需要把智能指针类声明为辅助类的友元。这个辅助类含有两个数据成员:计数count基础对象指针。也即辅助类用以封装使用计数与基础对象指针

class U_Ptr{
private:
	friend class SmartPtr;      
	U_Ptr(Point *ptr) : p(ptr), count(1) { }//创建的时候count初始化为1
	~U_Ptr() { delete p; }
	Point *p;
    int count;                                                         
};

为基础对象类实现智能指针类

引用计数是实现智能指针的一种通用方法。智能指针将一个计数器与类指向的对象相关联,引用计数跟踪共有多少个类对象共享同一指针。它的具体做法如下:

  • 当创建类的新对象时,初始化指针,并将引用计数设置为1
  • 当对象作为另一个对象的副本时,拷贝构造函数拷贝副本指针,并增加与指针相应的引用计数(加1)
  • 使用赋值操作符对一个对象进行赋值时,处理复杂一点:先使左操作数的指针的引用计数减1(为何减1:因为指针已经指向别的地方),如果减1后引用计数为0,则释放指针所指对象内存。然后增加右操作数所指对象的引用计数(为何增加:因为此时做操作数指向对象即右操作数指向对象)。
  • 析构函数:调用析构函数时,析构函数先使引用计数减1,如果减至0则delete对象。

做好前面的准备后,我们可以来为基础对象类Point书写一个智能指针类了。根据引用计数实现关键点,我们可以写出我们的智能指针类如下:

class SmartPtr{
public:
    SmartPtr(Point *ptr) : rp(new U_Ptr(ptr)) {}//构造函数
    SmartPtr(const SmartPtr &sp) : rp(sp.rp) { ++rp->count;}//复制构造函数
    SmartPtr& operator=(const SmartPtr& rhs){//重载赋值操作符
        ++rhs.rp->count;//首先将右操作数引用计数加1,
        if(--rp->count == 0)    delete rp;//然后将引用计数减1,可以应对自赋值
        rp = rhs.rp;
        return *this;
    } 
    ~SmartPtr(){//析构函数
        if(--rp->count == 0)    delete rp;//当引用计数减为0时,删除辅助类对象指针,从而删除基础对象
        else cout << "还有 " << rp->count << " 个指针指向基础对象" << endl;
    }

private:
    U_Ptr *rp;//辅助类对象指针
};

智能指针类的使用与测试

#include <iostream>
using namespace std;

class Point{
public:
	Point(int xVal = 0, int yVal = 0) :x(xVal), y(yVal) { }
	int getX() const { return x; }
	int getY() const { return y; }
	void setX(int xVal) { x = xVal; }
	void setY(int yVal) { y = yVal; }
private:
	int x, y;
};

class U_Ptr{
private:
	friend class SmartPtr;      
	U_Ptr(Point *ptr) : p(ptr), count(1) { }
	~U_Ptr() { delete p; }
	Point *p;
    int count;                                                         
};

class SmartPtr{
public:
    SmartPtr(Point *ptr) : rp(new U_Ptr(ptr)) {}//构造函数
    SmartPtr(const SmartPtr &sp) : rp(sp.rp) { ++rp->count;}//复制构造函数
    SmartPtr& operator=(const SmartPtr& rhs){//重载赋值操作符
        ++rhs.rp->count;//首先将右操作数引用计数加1,
        if(--rp->count == 0)    delete rp;//然后将引用计数减1,可以应对自赋值
        rp = rhs.rp;
        return *this;
    } 
    ~SmartPtr(){//析构函数
        if(--rp->count == 0)    delete rp;//当引用计数减为0时,删除辅助类对象指针,从而删除基础对象
        else cout << "还有 " << rp->count << " 个指针指向基础对象" << endl;
    }

private:
    U_Ptr *rp;//辅助类对象指针
};

int main(){
    Point *pa = new Point(10, 20);定义一个基础对象类指针
    //定义三个智能指针类对象,对象都指向基础类对象pa
	//使用花括号控制三个指针指针的生命期,观察计数的变化
    {
        SmartPtr sptr1(pa);//此时计数count=1
        {
            SmartPtr sptr2(sptr1);//调用拷贝构造函数,此时计数为count=2
            {
                SmartPtr sptr3 = sptr1;//调用拷贝赋值运算符,此时计数为conut=3
            }//此时count=2
        }//此时count=1
    }//此时count=0;pa对象被delete掉

    cout << pa->getX() << endl;//未定义的值

    return 0;
}

输出:
在这里插入图片描述
在离开大括号后,共享基础对象的指针从3->2->1->0变换,最后计数为0时,pa对象被delete,此时使用getX()已经获取不到原来的值。

智能指针类的改进一

虽然我们的SmartPtr类称为智能指针,但它目前并不能像真正的指针那样有->、*等操作符,为了使它看起来更像一个指针,我们来为它重载这些操作符。代码如下所示:

class SmartPtr{
public:
    SmartPtr(Point *ptr) : rp(new U_Ptr(ptr)) {}//构造函数
    SmartPtr(const SmartPtr &sp) : rp(sp.rp) { ++rp->count;}//复制构造函数
    SmartPtr& operator=(const SmartPtr& rhs){//重载赋值操作符
        ++rhs.rp->count;//首先将右操作数引用计数加1,
        if(--rp->count == 0)    delete rp;//然后将引用计数减1,可以应对自赋值
        rp = rhs.rp;
        return *this;
    } 
    ~SmartPtr(){//析构函数
        if(--rp->count == 0)    delete rp;//当引用计数减为0时,删除辅助类对象指针,从而删除基础对象
        else cout << "还有 " << rp->count << " 个指针指向基础对象" << endl;
    }
    Point& operator*() const {//重载*操作符  
        return *(rp->p);
    }
    Point* operator->() const {//重载->操作符  
        return rp->p;
    }

private:
    U_Ptr *rp;//辅助类对象指针
};

那么就可以实现:

        SmartPtr sptr1(pa);//此时计数count=1
        cout << sptr1->getX() << endl;
        cout << (*sptr1).getX() << endl;

智能指针改进二

目前这个智能指针智能用于管理Point类的基础对象,如果此时定义了个矩阵的基础对象类,那不是还得重新写一个属于矩阵类的智能指针类吗?但是矩阵类的智能指针类设计思想和Point类一样啊,就不能借用吗?答案当然是能,那就是使用模板技术。为了使我们的智能指针适用于更多的基础对象类,我们有必要把智能指针类通过模板来实现。这里贴上上面的智能指针类的模板版:

#include <iostream>
using namespace std;

//模板类作为友元时要先有声明
template <class T>
class SmartPtr;

template <class T>
class U_Ptr{
private://该类成员访问权限全部为private,因为不想让用户直接使用该类
	friend class SmartPtr<T>;//定义智能指针类为友元,因为智能指针类需要直接操纵辅助类
	U_Ptr(T *ptr) : p(ptr), count(1) { }
	~U_Ptr() { delete p; }
	T *p;//基础对象指针
    int count;//引用计数                                            
};

template <class T>
class SmartPtr{//智能指针类
public:
    SmartPtr(T *ptr) : rp(new U_Ptr<T>(ptr)) {}//构造函数
    SmartPtr(const SmartPtr &sp) : rp(sp.rp) { ++rp->count;}//复制构造函数
    SmartPtr& operator=(const SmartPtr& rhs){//重载赋值操作符
        ++rhs.rp->count;//首先将右操作数引用计数加1,
        if(--rp->count == 0)    delete rp;//然后将引用计数减1,可以应对自赋值
        rp = rhs.rp;
        return *this;
    } 
    T& operator*() const {//重载*操作符  
        return *(rp->p);
    }
    T* operator->() const {//重载->操作符  
        return rp->p;
    }
    ~SmartPtr(){//析构函数
        if(--rp->count == 0)    delete rp;//当引用计数减为0时,删除辅助类对象指针,从而删除基础对象
        else cout << "还有 " << rp->count << " 个指针指向基础对象" << endl;
    }

private:
    U_Ptr<T> *rp;//辅助类对象指针
};

int main(){
     int *i = new int(2);
    //定义三个智能指针类对象,对象都指向基础类对象pa
	//使用花括号控制三个指针指针的生命期,观察计数的变化
    {
        SmartPtr<int> ptr1(i);//此时计数count=1
        {
            SmartPtr<int> ptr2(ptr1);//调用拷贝构造函数,此时计数为count=2
            {
                SmartPtr<int> ptr3 = ptr1;//调用拷贝赋值运算符,此时计数为conut=3
                cout << *ptr1 << endl;
                *ptr1 = 20;
                cout << *ptr2 << endl;
            }//此时count=2
        }//此时count=1
    }//此时count=0

    return 0;
}

输出:在这里插入图片描述

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智能指针是一种用于自动管理动态分配内存的工具。其中最常见的智能指针C++中的std::shared_ptr和std::unique_ptr。下面是一个简单实现智能指针的示例: ```c++ #include <iostream> template <typename T> class SmartPointer { public: SmartPointer(T* ptr) : ptr_(ptr), ref_count_(new size_t(1)) { } SmartPointer(const SmartPointer<T>& other) : ptr_(other.ptr_), ref_count_(other.ref_count_) { ++(*ref_count_); } ~SmartPointer() { if (--(*ref_count_) == 0) { delete ptr_; delete ref_count_; } } SmartPointer<T>& operator=(const SmartPointer<T>& other) { if (this != &other) { if (--(*ref_count_) == 0) { delete ptr_; delete ref_count_; } ptr_ = other.ptr_; ref_count_ = other.ref_count_; ++(*ref_count_); } return *this; } T& operator*() const { return *ptr_; } T* operator->() const { return ptr_; } private: T* ptr_; size_t* ref_count_; }; int main() { SmartPointer<int> sp1(new int(5)); std::cout << *sp1 << std::endl; SmartPointer<int> sp2 = sp1; std::cout << *sp2 << std::endl; sp1 = SmartPointer<int>(new int(10)); std::cout << *sp1 << std::endl; std::cout << *sp2 << std::endl; return 0; } ``` 在上面的例子中,我们定义了一个模板类 SmartPointer,它保存了一个指向动态分配内存的原始指针 ptr_,以及一个引用计数 ref_count_。构造函数中,我们将引用计数初始化为1。当拷贝构造一个智能指针时,我们增加引用计数。当析构一个智能指针时,我们减少引用计数,并在引用计数变为0时释放内存。赋值运算符重载中,我们首先减少旧指针的引用计数,然后增加新指针的引用计数。 这只是一个简单智能指针实现示例,实际上,标准库中的std::shared_ptr和std::unique_ptr提供了更多的功能和安全性保证。但是这个实现可以帮助你理解智能指针的基本原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值