C++·智能指针

目录

使用场景

设计思路

C++标准库·智能指针

auto_ptr

unique_ptr

shared_ptr

weak_ptr

补充

删除器

decltype

循环引用

模拟实现

auto_ptr

unique_ptr

shared_ptr

weak_ptr

内存泄漏

小知识


使用场景

以下面程序为例,如果new时b抛了异常,那么会直接跳到主函数,虽然b没能成功申请空间,但a申请的空间资源就未能释放,造成内存泄漏,而给所有抛异常的都加上检查的话很麻烦,所以使用了智能指针来处理。

设计思路

RAII(Resource Acquisition Is Initialization):一种管理资源的类的设计思想,本质是一种利用对象生命周期来管理获取到的动态资源,避免资源泄露,这里的资源可以是内存、文件指针、网络连接、互斥锁等。

RAII在获取资源时将其委托给一个对象,接着控制对资源的访问,资源在对象的生命周期内始终保持有效,最后在对象析构时释放资源,保证资源正确释放。

例:

//用智能指针对象托管new的指针,等到该对象生命周期结束就会将new出来的资源一起销毁

template<class T>
class Smartptr
{
    T* _ptr;
public:
    Smartptr(T* ptr)
        :_ptr(ptr)
    {}

    ~Smartptr()
    {
        delete[] _ptr;
    }

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }

    T& operator[](size_t i)
    {
        return _ptr[i];
    }
};

double Divide(int a, int b)
{
    // 当b == 0时抛出异常 
    if (b == 0)
    {
        throw "Divide by zero condition!";
    }
    else
    {
        return (double)a / (double)b;
    }
}


void Func()
{
    // 这⾥使⽤RAII的智能指针类管理new出来的数组以后,程序简单多了 
    Smartptr<int> sp1 = new int[10];
    Smartptr<int> sp2 = new int[10];

    for (size_t i = 0; i < 10; i++)
    {
        sp1[i] = sp2[i] = i;
    }
    int len, time;
    cin >> len >> time;
    cout << Divide(len, time) << endl;
}


int main()
{
    try
    {
        Func();
    }
    catch (const char* errmsg)
    {
        cout << errmsg << endl;
    }
    catch (const exception& e)
    {
        cout << e.what() << endl;
    }
    catch (...)
    {
        cout << "未知异常" << endl;
    }
    return 0;
}

C++标准库·智能指针

都包含在<memory> 这个头文件中

auto_ptr

是C++98时设计出来的智能指针,他的特点是拷贝时把被拷贝对象的资源的管理权转移给拷贝对象,这是⼀个非常糟糕的设计,因为他会导致被拷贝对象悬空,访问报错的问题,C++11设计出新的智能指针后,强烈建议不要使用auto_ptr。

unique_ptr

是C++11设计出来的智能指针,他的名字翻译出来是唯⼀指针,他的特点的不支持拷贝,只支持移动。如果不需要拷贝的场景就非常适合使用。

shared_ptr

是C++11设计出来的智能指针,他的名字翻译出来是共享指针,他的特点是支持拷贝, 也支持移动。如果需要拷贝的场景就需要使用他了,底层中使用了引用计数。

weak_ptr

是C++11设计出来的智能指针,他的名字翻译出来是弱指针,他完全不同于上面的智能指
针,他不支持RAII,也就意味着不能用它直接管理资源,weak_ptr的产生本质是要解决shared_ptr的⼀个循环引用导致内存泄漏的问题。

注:1.未重载operator*和operator->,因为它不参与资源管理。

2.支持使用expired检查指向的资源是否过期

3.可以调用lock返回一个管理资源的shared_ptr,如果资源已释放返回一个空shared_ptr对象。

补充

1.智能指针析构时默认是进行delete释放资源,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。


2.智能指针支持在构造时给⼀个删除器,所谓删除器本质就是⼀个可调用对象,这个可调用对象中实现你想要的释放资源的方式,当构造智能指针时,给了定制的删除器,在智能指针析构时就会调用删除器去释放资源。


3.shared_ptr 除了支持用指向资源的指针构造,还支持 make_shared用初始化资源对象的值直接构造。


4.shared_ptr 和 unique_ptr 都支持了operator bool的类型转换,如果智能指针对象是⼀个空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空。


5.shared_ptr 和 unique_ptr 构造函数都使用了explicit修饰,防止普通指针隐式类型转换成智能指针对象。

explicit:用来修饰类的构造函数,以防止编译器进行隐式的类型转换或自动调用构造函数,从而避免潜在的错误或误解。

删除器

 // 这样实现程序会崩溃 ,T为未实现析构的一个自定义类型
 // unique_ptr<T> up1(new T[10]);
 // shared_ptr<T> sp1(new T[10]);

解决有两种方法:

1.因为new[]经常使⽤,所以unique_ptr和shared_ptr实现了⼀个特化版本,这个特化版本析构时⽤的delete[]

 unique_ptr<T[]> up1(new T[5]);
 shared_ptr<T[]> sp1(new T[5]);

2.做删除器

注: 

1.unique_ptr和shared_ptr⽀持删除器的⽅式有所不同,unique_ptr是在类模板参数⽀持的,shared_ptr是构造函数参数⽀持的 
2.使⽤仿函数unique_ptr可以不在构造函数传递,因为仿函数类型构造的对象直接就可以调⽤,但是函数指针和lambda的类型不可以

例:

template<class T>
class Deletetool
{
public:
    void operator()(T* t)
    {
        delete[] t;
    }
};

template<class T>
void Deletefun(T* t)
{
    delete[] t;
}

class Date
{
    int _year;
    int _month;
public:
    Date()
        :_year(0)
        ,_month(0)
    { }
    Date(int year,int month)
        :_year(year)
        ,_month(month)
    {}

    ~Date()
    {
        ;
    }
};


int main()
{
    //仿函数
    unique_ptr<Date,Deletetool<Date>> up(new Date[10]);
    shared_ptr<Date> sp(new Date[10], Deletetool<Date>());

    //函数指针
    unique_ptr<Date,void(*)(Date*)> up2(new Date[10],Deletefun<Date>);
    shared_ptr<Date> sp2(new Date[10], Deletefun<Date>);

    //lambda表达式
    auto Deletelambda = [](Date* ptr) {delete[] ptr; };
    unique_ptr<Date,decltype(Deletelambda)> up3(new Date[10],Deletelambda);
    shared_ptr<Date> sp3(new Date[10], Deletelambda)


    return 0;
}

decltype

编译时推导表达式类型

循环引用

 两个对象都有智能指针成员next和prev,当有智能指针先指向它们,并且它们相互指向时。

这时,就需要使用weak_ptr,将结点结构体中的next和prev指针由shared_ptr改为weak_ptr,让它们相互指向时,引用计数不会增加。 

weak_ptr专门用来解决这类问题。

模拟实现

auto_ptr

template<class T>
class auto_ptr
{
    T* _ptr;
public:
    explicit auto_ptr(T* ptr)
        :_ptr(ptr)
    {}

    auto_ptr(auto_ptr<T>& p)
        :_ptr(p._ptr)
    {
        p._ptr = nullptr;
    }

    auto_ptr<T>& operator=(auto_ptr<T>& p) 
    {
        if (_ptr != p._ptr)
        {
            if (_ptr)
                delete _ptr;
            _ptr = p._ptr;
            p._ptr = nullptr;
        }
        return *this;
    }

    ~auto_ptr()
    {
        if(_ptr)
            delete _ptr;
    }

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }


};

unique_ptr

template<class T>
class unique_ptr
{
    T* _ptr;
public:
    explicit unique_ptr(T* ptr)
        :_ptr(ptr)
    {}

    unique_ptr(unique_ptr<T>&& tt) noexcept
        :_ptr(tt._ptr)
    {
        tt._ptr = nullptr;
    }


    unique_ptr<T>& operator=(unique_ptr<T>&& tt)
    {
        if (_ptr != tt._ptr)
        {
            if (_ptr)
                delete _ptr;
            _ptr = tt._ptr;
            tt._ptr = nullptr;
        }
        return *this;
    }


    unique_ptr(const unique_ptr<T>& t) = delete;
    unique_ptr<T>& operator=(const unique_ptr<T>& t) = delete;

    ~unique_ptr()
    {
        if (_ptr)
            delete _ptr;
    }
    
    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }


};

shared_ptr

    template<class T>
    class shared_ptr
    {
        T* _ptr;
        int* _count;

        function<void(T*)> _del = [](T* ptr) {delete ptr; };//function是functional头文件中的函数,使用时记得包头文件
    public:
        explicit shared_ptr(T* ptr = nullptr)
            :_ptr(ptr)
            ,_count(new int(1))
        {}

        template<class D>
        shared_ptr(T* ptr,D del)
            :_ptr(ptr)
            ,_count(new int(1))
            ,_del(del)
        { }

        shared_ptr(const shared_ptr<T>& t)
        {
            _ptr = t._ptr;
            _count = t._count;
            (*_count)++;
        }

        void release()
        {
            if (--(*_count) == 0)
            {
                _del(_ptr);
                delete _count;
                _ptr = nullptr;
                _count = nullptr;
            }
        }

        shared_ptr<T> operator=(const shared_ptr<T>& t)
        {
            if (_ptr != t._ptr)
            {
                release();
                _ptr = t._ptr;
                _count = t._count;
                (*_count)++;
            }
            return *this;
        }

        ~shared_ptr()
        {
            release();
        }

        T* get() const
        {
            return _ptr;
        }

        int use_count() const
        {
            return *_count;
        }

        T& operator*()
        {
            return *_ptr;
        }

        T* operator->()
        {
            return _ptr;
        }

    };
 

weak_ptr

template<class T>
class weak_ptr
{
    T* _ptr;
    
public:
    weak_ptr()
    {}

    weak_ptr(const shared_ptr<T>& st)
        :_ptr(st.get())
    {}

    weak_ptr<T>& operator=(const shared_ptr<T>& st)
    {
        _ptr = st.get();
        return *this;
    }

};

内存泄漏

 内存泄漏:指因为疏忽或错误造成程序未能释放已经不在使用的内存,一般是忘记释放或发生异常释放的程序未能执行导致。

并不是指内存在物理上的消失,而是程序分配空间后,因为设计错误失去了对这段空间的控制,造成内存浪费。

内存泄漏的危害:普通程序运行⼀会就结束了出现内存泄漏问题也不大,进程正常结束,页表的映射关系解除,物理内存也可以释放。长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务、长时间运行的客户端等等,不断出现内存泄漏会导致可用内存不断变少,各种功能响应越来越慢,最终卡死。

小知识

1.final可以修饰类和虚函数,表示该类/该虚函数不能被继承/重写

2.范围for在遍历时某些情况会将'\0‘也一起遍历。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值