智能指针与引用计数详解(一)

前言
在C++项目开发中,有时候会遇到悬垂指针的问题,其中提供的解决方案推荐使用智能指针。
在《C++Primer》书13.5章 提到了该技术的具体使用场景和实现。通过反复阅读和代码测试终于了解其中的思想,下面谈谈自己的理解,如果发现有什么问题,请大家批评指正。

什么是智能指针?
智能指针其实就是一个类,除了增加了一些功能外,其因为跟跟普通指针一样。通常情况,new一个对象会申请一块内存,复制或者赋值时,会导致多个指针指向同一个内存区域,如果对其中任意一个指针进行操作,比如修改指针指向的值,那么其它指针也指向的值都会改变,如果删除了其中一个指针所指向的对象,那么其它指针所指向的对象也都不存在了,这些指针也就变成悬垂指针了。另外如果new出的对象还没有到delete方法执行那么就好造成内存泄漏。智能指针主要目的就是管理这块内存,避免内存泄漏和悬垂指针。

为什么要使用智能指针?
上述说得对象复制或者赋值的时候会导致多个指针指向同一个内存区域,那么只要我们重写这个两个方法,采用深复制那么就不会出现该问题了吧。答案确实是可以的,但是这样如果被复制的对象较大并且使用很频繁,同时只是获取对象值的情况,这样做无疑增加了内存开销也影响性能。所以这时候可以考虑智能指针实现。

普通指针的问题
一个带有简单指针的类:
图示:
在这里插入图片描述
代码测试:
hasPtr.h

#ifndef _HasPtr_H
#define _HasPtr_H

class HasPtr {

    public:
        HasPtr(int *p = 0, int i = 0);
        ~HasPtr();
        int *get_ptr() const;
        void set_ptr(int *p);

        int get_int() const;
        void set_int(int i);

        int get_ptr_val() const;
        void set_ptr_val(int val);

    private:
        int *ptr;
        int val;

};
#endif

hasPtr.cpp

#include "hasPtr.h"
#include <iostream>
using namespace std;

HasPtr::HasPtr(int *p, int i)
    :ptr(p)
    ,val(i)
{

}

int *HasPtr::get_ptr() const {
    return ptr;
}

void HasPtr::set_ptr(int *p) {
    ptr = p;
}

int HasPtr::get_int() const {
    return val;
}

void HasPtr::set_int(int i) {
    val = i;
}

int HasPtr::get_ptr_val() const {
    return *ptr;
}

void HasPtr::set_ptr_val(int val) {
    *ptr = val;
}

HasPtr::~HasPtr() {
    cout<<"HasPtr destruct"<<endl;
    if (NULL != ptr) {                      //保护处理,如果不判空,使用者如果使用不当会造成多次delete,会报错
        delete ptr;
        ptr = NULL;
    }
}

指针共享同一个对象:

    int obj = 10;
    HasPtr ptr1(&obj, 42);
    HasPtr ptr2(ptr1);
    ptr2.set_ptr_val(20);
    int ptr_val2 = ptr2.get_ptr_val();
    cout<<"ptr_val2 = "<<ptr_val2<<endl;

    int ptr_val1 = ptr1.get_ptr_val();
    cout<<"ptr_val1 = "<<ptr_val1<<endl;

输出结果都是20

出现悬垂指针:

    int *ip = new int(20);
    HasPtr ptr(ip, 1);
    delete ip;
    cout<<"get_ptr = "<<ptr.get_ptr_val()<<endl;

输出结果是0
由于delete ip,所以ip指向的对象被回收,但是ptr里边的ip还存在,并且不知道其指向的对象已经被回收了。

定义智能指针类
智能指针的实现策略有两种:引用计数类与句柄类(《C++Primer》15.8.1)。这里介绍引用计数类的实现方法。

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

二、引用计数类
定义一个具体类(U_Ptr)来封装引用计数和指针。在创建智能指针类之前,这个类的所有成员皆为私有类型,因为它不被普通用户所使用。为了只为智能指针使用,还需要把智能指针类声明为该类的友元。类中包含含两个数据成员:计数count与之前HasPtr类中的指针( int *ptr;)。
图示:
在这里插入图片描述

引用计数类:
uPtr.h

#ifndef _UPtr_H
#define _UPtr_H

class HasPtr;
class U_Ptr {
    private:
    friend class HasPtr;

    U_Ptr(int *p);
    ~U_Ptr();
    
    int *m_ip;
    int m_useCount;
};
#endif

uPtr.cpp

#include "uPtr.h"
#include <iostream>
using namespace std;

U_Ptr::U_Ptr(int *p)
    :m_ip(p)
    ,m_useCount(1)
{

}

U_Ptr::~U_Ptr()
{
    cout<<"U_Ptr destruct"<<endl;
    if(NULL != m_ip) {
    	delete m_ip;
    	m_ip = NULL;
    }
}

智能指针类实现:
hasPtr.h

#ifndef _HasPtr_H
#define _HasPtr_H

#include "uPtr.h"

class HasPtr {

    public:
        HasPtr(int *p = 0, int i = 0);
        HasPtr(const HasPtr &ptr);
        HasPtr& operator=(const HasPtr &rhs);
        ~HasPtr();

        int *get_ptr() const;
        void set_ptr(int *p);

        int get_int() const;
        void set_int(int i);

        int get_ptr_val() const;
        void set_ptr_val(int val);

    private:
        int m_val;
        U_Ptr *m_uptr;

};
#endif

hasPtr.cpp

#include "hasPtr.h"
#include <iostream>
using namespace std;

HasPtr::HasPtr(int *p, int i)
    :m_uptr(new U_Ptr(p))
    ,m_val(i)
{
    cout<<"HasPtr  constructor"<<endl;
}

HasPtr::HasPtr(const HasPtr &ptr)
    :m_uptr(ptr.m_uptr)
    ,m_val(ptr.m_val)
{
    cout<<"HasPtr copy constructor m_uptr->m_useCount = "<<m_uptr->m_useCount<<endl;
    ++m_uptr->m_useCount;
}

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
    cout<<"HasPtr assignment rhs.m_uptr->m_useCount = "<<rhs.m_uptr->m_useCount<<endl;
    cout<<"HasPtr assignment m_uptr->m_useCount = "<<m_uptr->m_useCount<<endl;
    ++rhs.m_uptr->m_useCount;
    if (--m_uptr->m_useCount == 0) {
        delete m_uptr;
    }
    m_uptr = rhs.m_uptr;
    m_val = rhs.m_val;
    return *this;
}

int *HasPtr::get_ptr() const {
    return m_uptr->m_ip;
}

void HasPtr::set_ptr(int *p) {
    m_uptr->m_ip = p;
}

int HasPtr::get_int() const {
    return m_val;
}

void HasPtr::set_int(int i) {
    m_val = i;
}

int HasPtr::get_ptr_val() const {
    return *m_uptr->m_ip;
}

void HasPtr::set_ptr_val(int val) {
    *m_uptr->m_ip = val;
}

HasPtr::~HasPtr() {
    cout<<"HasPtr destruct m_uptr->m_useCount = "<<m_uptr->m_useCount<<endl;
    if (--m_uptr->m_useCount == 0) {
        delete m_uptr;
        m_uptr = NULL;
    }
}

这里赋值函数比较复杂,左操作数减一是因为指向新对象,那么之前指向的对象应该被回收,右操作数加一是因为该操作数已经指向了这个对象,现在即将又有一个新指针(左操作数)指向该对象。

main()

    int *ip = new int(12);
    HasPtr ptr(ip, 20);            //执行构造函数
    HasPtr ptr1(ptr);              //执行复制构造函数
    HasPtr ptr2(ptr);             //执行复制构造函数
    // 下面使用花括号是为了控制HasPtr对象生命周期
    {
        HasPtr ptr3 = NULL;      //执行构造函数
        ptr3 = ptr;              //执行赋值函数
        // HasPtr ptr3 = ptr;    //注意,这里调用的是复制构造函数,因为有新对象创建
    }

输出结果:
U_Ptr constructor
HasPtr constructor
HasPtr copy constructor m_uptr->m_useCount = 1
HasPtr copy constructor m_uptr->m_useCount = 2
U_Ptr constructor
HasPtr constructor
HasPtr assignment rhs.m_uptr->m_useCount = 3
HasPtr assignment m_uptr->m_useCount = 1
U_Ptr destruct //左操作数指向的对象被回收
HasPtr destruct m_uptr->m_useCount = 4
HasPtr destruct m_uptr->m_useCount = 3
HasPtr destruct m_uptr->m_useCount = 2
HasPtr destruct m_uptr->m_useCount = 1
U_Ptr destruct //删除指针

从结果来看,当程序执行完毕时对象会被全部回收掉,这里直到最后才真正删除指针。

demo代码

扩展:
测试过程中发现智能指针的赋值方法其实还可以改进,另外智能指针也不应该仅限于一个具体指针(C++模板)。
具体请见 智能指针与引用计数详解(二)

  • 14
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值