要是面试官再问你智能指针的问题,就拿这篇文章“盘他”!!!

简介: 要是面试官再问你智能指针的问题,就拿这篇文章“盘他”!!!

前一段时间,有不少朋友问我关于智能指针的问题,并且反映经常会在面试中被面试官问到,所以今天小豆君就来讲讲我对智能指针的理解,希望能对大家有所帮助

既然讲智能指针,我们就先来看看它为什么会出现。

1 传统指针存在的问题

刚学C++的朋友,要数最令人头疼的问题莫过于指针了。

当你在堆上创建了一个对象时,系统就把这个对象的生命期完全交给了你,当用完之后,系统并不会回收资源,而是需要你来释放它。

那么,既然要负责对象的释放问题,就要知道什么时候释放和在哪里释放。如果你没有处理好这两个问题,就会造成内存泄漏或程序崩溃的问题。

//1 内存泄漏 str1所指的资源没有被释放
{
    string* str1 = new string("hello");
    string* str2 = new string("world");
}
//2 多重释放,引起程序崩溃
{
    string* str1 = new string("hello");
    delete str1;
    //...
    delete str1;//引起程序崩溃
}


虽然上述例子很简单,指针的释放是在同一作用域中进行的,大家在编程中也能很容易避免上述代码中的问题。

但是,对于一个大型项目,在某一处创建的对象,可能并不会在对应作用域中释放,而是等到某些事件发生,异常处理等情况下才会去销毁对象,对于这样的问题往往是很难排查出来的。

所以,有必要引用一种机制来负责指针的自动销毁。而不是由程序员本身去手动销毁。智能指针恰恰就是这样的一种机制。

2 基于引用计数的智能指针原理分析

下面的这张图,解释了智能指针的原理。



1.当从堆上申请了一个资源时,我们就创建一个智能指针对象,使它指向这个资源,同时,在堆上申请一个用于计数的资源,让后来所有的指向该资源的对象都共享这个计数资源,这样,引用计数的个数就只有一份。

2.当将ptr1对象赋值给对象ptr2时,其共享的引用计数变为2。

3.删除ptr2对象时,其对应的引用计数减为1。

4.删除ptr1对象时,引用计数变为0,则释放资源。

3 智能指针的一种实现方式

在写代码前,先明确下代码的目的:

首先,智能指针是一个类,这样就可以使用构造函数和析构函数对引用计数进行维护;

其次,它要表现出指针的行为,并且使用起来也要像普通指针一样;

最后,智能指针对任何类型都可以使用,所以它应该是一个模板。

在阅读代码的时候,可以参考智能指针原理图。 代码并不困难,并且小豆君都做了注释

#pragma once
template<class T>
class SharedPointer
{
public:
    //默认构造函数,内部指针,未指向任何资源,引用计数为0,因为它未与任何资源绑定
    SharedPointer() :m_refCount(nullptr), m_pointer(nullptr){}
    //构造函数,初始化时,指向一个已经分配好的资源
    SharedPointer(T* adoptTarget) :m_refCount(nullptr), m_pointer(adoptTarget)
    {
        addReference();
    }
    //构造函数,使用其它对象创建新对象
    SharedPointer(const SharedPointer<T>& copy)
        :m_refCount(copy.m_refCount), m_pointer(copy.m_pointer)
    {
        addReference();
    }
    //析构函数,引用计数递减,当为0时,释放资源
    virtual ~SharedPointer()
    {
        removeReference();
    }
    //赋值操作
    //当左值被赋值时,表明它不再指向所指的资源,故引用计数减一
    //之后,它指向了新的资源,所以对应这个资源的引用计数加一
    SharedPointer<T>& operator=(const SharedPointer<T>& that)
    {
        if (this != &that)
        {
            removeReference();
            this->m_pointer = that.m_pointer;
            this->m_refCount = that.m_refCount;
            addReference();
        }
        return *this;
    }
    //判断是否指向同一个资源
    bool operator==(const SharedPointer<T>& other)
    {
        return m_pointer == other.m_pointer;
    }
    bool operator!=(const SharedPointer<T>& other)
    {
        return !operator==(other);
    }
    //指针解引用
    T& operator*() const
    {
        return *m_pointer;
    }
    //调用所知对象的公共成员
    T* operator->() const
    {
        return m_pointer;
    }
    //获取引用计数个数
    int GetReferenceCount() const
    {
        if (m_refCount)
        {
            return *m_refCount;
        } 
        else
        {
            return -1;
        }
    }
protected:
    //当为nullpter时,创建引用计数资源,并初始化为1
    //否则,引用计数加1。
    void addReference()
    {
        if (m_refCount)
        {
            (*m_refCount)++;
        }
        else
        {
            m_refCount = new int(0);
            *m_refCount = 1;
        }
    }
    //引用计数减一,当变为0时,释放所有资源
    void removeReference()
    {
        if (m_refCount)
        {
            (*m_refCount)--;
            if (*m_refCount == 0)
            {
                delete m_refCount;
                delete m_pointer;
                m_refCount = 0;
                m_pointer = 0;
            }
        }
    }
private:
    int * m_refCount;
    T   * m_pointer;
};



4 测试

以下程序是对上述代码的测试,大家可以看看实际的运行效果

#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "SharedPointer.h"
using namespace std;
class MyClass
{
public:
    ~MyClass()
    {
        cout << "释放MyClass(" << _id << ")\n";
    }
    MyClass(int i) :_id(i)
    {
    }
    void Print() const
    {
        cout << "MyClass(" << _id << ")" << endl;
    }
private:
    int _id;
};
int main()
{
    {
        MyClass* px = new MyClass(1);
        SharedPointer<MyClass> ap(px);
        SharedPointer<MyClass> bp = ap;
        SharedPointer<MyClass> cp;
        cout << "ap的引用计数(2): "
             << ap.GetReferenceCount() << endl;
        cout << "将ap赋值给cp\n";
        cp = ap;
        cout << "ap的引用计数(3): "
             << ap.GetReferenceCount() << endl;
        MyClass* qx = new MyClass(5);
        SharedPointer<MyClass> dp(qx);
        ap = dp;
        cout << "ap的引用计数(2): "
             << ap.GetReferenceCount() << endl;
        cout << "dp的引用计数(2): "
             << dp.GetReferenceCount() << endl;
        //"像指针一样使用智能指针"
        dp->Print();
        (*cp).Print();
    }
    cin.get();
}


运行结果:


我们跟踪测试程序,通过运行结果,查看引用计数的变化,并且,当计数为0时,自动释放资源。

好了,关于智能指针的原理示例程序就给大家介绍到这里了,以后再有面试官问你,就可以明明白白的给他解释了。其实,标准库中也有对应库实现shared_ptr,而且它比小豆君的要复杂多了。

智能指针虽然在一定程序上解决了裸指针存在的问题,但是还是有很多不足的地方,例如本例还缺少资源共享时的保护措施,环形引用等问题,等有机会小豆君再跟大家分享吧。


欢迎关注微信公众号-小豆君Qt分享

相关文章
|
7月前
|
安全 程序员 编译器
C++中的RAII(资源获取即初始化)与智能指针
C++中的RAII(资源获取即初始化)与智能指针
88 0
|
7月前
|
安全 程序员 C++
C++中的智能指针:从原始指针到现代内存管理
C++中的智能指针:从原始指针到现代内存管理
53 0
|
7月前
|
C++
C/C++工程师面试题(指针篇)
C/C++工程师面试题(指针篇)
104 0
|
7月前
|
算法 程序员 索引
【Leetcode 程序员面试金典 02.08】 —— 环路检测 |双指针
我们可以使用双指针解决本题,由数学推导可知:a 的距离为(环长度的倍数 - b),即 tmp 指针从头节点走到环开头节点等于 slow 指针走到环开头节点的距离
|
7月前
|
C++
C++:一文读懂智能指针
C++:一文读懂智能指针
105 0
|
7月前
|
安全 C++ 容器
C++中的智能指针:自动内存管理的利器
C++中的智能指针:自动内存管理的利器
89 0
|
3月前
|
存储 负载均衡 Java
Elasticsearch集群面试系列文章一
【9月更文挑战第9天】Elasticsearch(简称ES)是一种基于Lucene构建的分布式搜索和分析引擎,广泛用于全文搜索、结构化搜索、分析以及日志实时分析等场景。
109 7
|
6月前
|
存储 调度 C++
【操作系统】进程与线程的区别及总结(非常非常重要,面试必考题,其它文章可以不看,但这篇文章最后的总结你必须要看,满满的全是干货......)
【操作系统】进程与线程的区别及总结(非常非常重要,面试必考题,其它文章可以不看,但这篇文章最后的总结你必须要看,满满的全是干货......)
133 1
|
2月前
|
编译器 C语言
经典左旋,指针面试题
文章介绍了两种C语言实现字符串左旋的方法,以及如何使用C语言对整数数组进行奇偶数排序。通过实例演示了如何使用函数reverse_part和leftRound,以及在swap_arr中实现数组元素的重新排列。
28 0
|
2月前
|
Serverless 编译器 C语言
【C语言】指针篇- 深度解析Sizeof和Strlen:热门面试题探究(5/5)
【C语言】指针篇- 深度解析Sizeof和Strlen:热门面试题探究(5/5)