以下内容为本人的学习笔记,如需求转载,请声明原文链接 微信大众号「ENG八戒」mp.weixin.qq.com/s/XIWsV7Lab…

首要,提个问题,何为引证计数,为什么要去完成引证计数?

智能指针最大的作用便是帮忙开发者管理内存,防止开发者遗漏开释或者错误开释已申请的内存空间,最大极限确保内存安全。运用智能指针可免除十分多的经典 C++ 版别带来的烦恼,比如招人恨的 double free or corruption。

据微软内部计算,C++ 应用的绝大部分问题归于内存安全问题,所以保证内存安全的办法真的十分重要。

那么,智能指针是怎么确保内存安全的呢?

智能指针在初始化时会保管传入的内存空间,在后续的流程中,一旦识别到被保管的内存不再被需求的时候,主动开释这块内存。

怎么识别被保管的内存是否还被需求?

经过引证计数,引证计数便是计数器,一般用整形变量表示,代表有多少个智能指针已保管该内存空间。

已保管的内存每增加绑定一个智能指针,该计数器主动加 1;反之,已保管的内存每解绑一个智能指针(比如智能指针超出生命周期,或者主动解除绑定),该计数器主动减 1。直到计数器归 0,这时被保管的内存即被判别为不再需求保存,最终解绑的智能指针负责开释该内存空间。

其间,可见该计数器被所有绑定的智能指针同享拜访。

虽然标准库现已提供十分多现成的智能指针可供调用,可是其间的奥妙还是十分值得咱们细细揣摩,下面就开端跟从笔者一同完成自己的引证计数帮助类,权当作略微简陋的自界说智能指针。

先界说两个类,一个是代表占用内存资源的类 Demo,另一个是便是咱们自界说的智能指针类 SmartPtr

class Demo
{};
class SmartPtr
{};

资源类附加特点

因为绑定同一个内存资源的所有智能指针是同享拜访该内存的,所以能够在类 Demo 中附加界说计数器 count_,计数器作为专用特点应该带有隐私特点也便是不允许随意被类外拜访,所以同时应该被修饰为 private。

在类 Demo 实例化时,计数器 count_ 应该被清零,然后才能被 SmartPtr 保管。所以要求类 Demo 的所有结构函数都初始化 count_ 为 0。

class Demo
{
public:
    Demo() : count_(0) {};
private:
    unsigned int count_;
};

作为保管方的类 SmartPtr 需求有权限能够直接读写类 Demo 的私有成员 count_,所以在类 Demo 中还要声明类 SmartPtr 为友元类

class Demo
{
    // ...
private:
    // ...
    friend class SmartPtr;
};

关于代表资源的类的简略改造就介绍完了。

自界说智能指针类

智能指针类 SmartPtr 作为保管方,需求完成基本的功能接口,包括保管、解绑、拜访资源成员、获取资源引证等。

保管

智能指针需求保管某个内存资源,那么就需求在内部有对应的指针指向资源。相同为了不必要的露出,这个指针需求用 private 修饰。

class SmartPtr
{
private:
    Demo *p_;
};

在保管资源时,资源对应的引证计数器应该自增 1,因为咱们界说的智能指针在资源类的内部被声明为友元类,所以智能指针绑定资源时能够直接读写资源的计数器。

智能指针怎么绑定资源,或者说怎么接纳将要被保管的资源呢?

资源未被保管

假如资源当时没有被任何智能指针保管,那么,能够在实例化智能指针目标时,运用智能指针的结构函数接纳资源指针,并保存到内部指针变量,依照上面的代码便是保存到变量 p_。

class SmartPtr
{
    // ...
public:
    SmartPtr(Demo *p) : p_(p) {
        ++ p_->count_;
    }
};

共享保管

假如资源当时正在被其它智能指针保管,那么,能够从其它的智能指针共享过来。

有两种共享的方法,一是创建一个新的智能指针来接纳其它智能指针的共享,需求用到拷贝结构函数,并传入其它的智能指针的引证实例。

class SmartPtr
{
    // ...
public:
    SmartPtr(const SmartPtr &obj) : p_(obj.p_) {
        ++ p_->count_;
    }
};

二是,假如有个智能指针实例当时现已保管某个资源,可是需求从头绑定其它资源,而且当时保管的资源需求解绑,能够运用拷贝赋值运算符,右侧操作数便是其它共享资源的智能指针,左边操作数是当时的智能指针。

解绑当时保管的资源,除了要清除原有的资源指针备份,还需求对资源的计数器减 1,而且判别计数器减 1 后是否归零,假如归零就需求开释该资源占用的内存

class SmartPtr
{
    // ...
public:
    SmartPtr& operator=(const SmartPtr& sp) {
        Demo *p = p_;
        p_ = sp.p_;
        ++ p_->count_;
        if (0 == (-- p->count_)) {
            delete p;
        }
        return *this;
    }
};

生命周期的结束

上面说到替换保管资源时,还需求解绑本身原有保管的资源。其实当智能指针目标本身的生命周期结束之时,也便是调用释构函数时,也需求解绑本身原有保管的资源,这时的解绑主要做的便是对资源的计数器减 1,而且判别计数器减 1 后是否归零,假如归零就需求开释该资源占用的内存

class SmartPtr
{
    // ...
public:
    ~SmartPtr() {
        if (0 == (-- p_->count_)) {
            delete p_;
        }
    }
};

成员拜访

智能指针在保管资源后,资源内部的成员应该能经过智能指针来拜访,正如前面介绍资源类时,在资源类内部声明智能指针为友元类。经过智能指针来拜访资源类内部成员的方法,应该类似目标的指针拜访目标成员,需求经过重写智能指针的成员拜访运算符来完成

class SmartPtr
{
    // ...
public:
    Demo* operator->() {
        return p_;
    }
};

operator-> () 是一元右缀操作符,重写了类成员拜访运算符,回来被保管的资源指针。

当智能指针目标调用该操作符时,比如 p 被声明为智能指针实例目标,被保管的资源所属类包含成员 m,那么 p->m 会被编译器解析成 ((p.operator->)->m

获取资源引证

一般经过目标指针获取目标引证时,是经过 * 运算符。类似地,需求获取被保管资源目标的引证,能够重写 * 运算符完成

class SmartPtr
{
    // ...
public:
    Demo& operator*() {
        return *p_;
    }
};

防止露出裸指针

一般不推荐直接调用资源类的裸指针,尽量防止重写智能指针的操作符以回来被保管资源的指针,因为露出的裸指针会被意外地运用而损坏引证计数的机制,最终损坏完成内存安全的努力。

而上面在设计资源类时,仍然依赖运用 new 操作符创建资源实例并回来裸指针,这无疑是一颗定时炸弹。为了隐藏好裸指针,能够把结构函数声明为 private,而且增加静态成员接口 create() 回来智能指针目标

// .h
class SmartPtr;
class Demo
{
public:
    static SmartPtr create();
private:
    Demo() : count_(0) {};
    unsigned int count_;
    friend class SmartPtr;
};
// ...
// .cpp
SmartPtr Demo::create() {
    return new Demo();
}

为什么接口 Demo::create() 内部直接 return 类 Demo 目标指针而不是 SmartPtr 目标?

因为前面完成类 SmartPtr 时,它的结构函数就有输入 Demo 指针的重载方法。所以接口 Demo::create() 声明回来类型为 SmartPtr 目标时,假如直接回来类 Demo 目标指针,就会隐式调用类 SmartPtr 的对应结构函数创建实例目标并回来。


基于上面的设计成果,当需求在堆里创建 Demo 实例时,内存安全的运用方法就能够是这姿态

SmartPtr ptr(Demo::create());

简简略单的思路剖析如上,假如各位同学朋友有什么疑问能够向我提,下拉到文章底部有我的联系方法。

最终,十分感激各位朋友的点 「赞」 和点击 「在看」,谢谢!