【C++ 学习 ㊱】- 智能指针详解

目录

一、为什么需要智能指针?

二、智能指针的原理及使用

三、auto_ptr

3.1 - 基本使用

3.2 - 模拟实现

四、unique_ptr

4.1 - 基本使用

4.2 - 模拟实现

五、shared_ptr

5.1 - 基本使用

5.2 - 模拟实现

六、weak_ptr

6.1 - shared_ptr 的循环引用问题

6.2 - 模拟实现

七、定制删除器



一、为什么需要智能指针?

问题引入:

#include 
using namespace std;
​
int division(int x, int y)
{
    if (y == 0)
        throw "Division by zero condition!";
    else
        return x / y;
}
​
void func()
{
    string* p1 = new string("hello");
    pair* p2 = new pair{ "hello", "你好" };
​
    int a = 0, b = 0;
    cin >> a >> b;
    cout << division(a, b) << endl;
​
    delete p1;
    delete p2;
}
​
int main()
{
    try {
        func();
    }
    catch (const char* errmsg) {
        cout << errmsg << endl;
    }
    catch (...) {
        cout << "Unknow exception" << endl;
    }
    return 0;
}

调用 func 函数:

  1. 如果在执行 string* p1 = new string("hello"); 语句的过程中抛出了异常,那么在 main 函数中会捕获到抛出的异常;

  2. 如果在执行 pair* p2 = new pair{ "hello", "你好" }; 语句的过程中抛出了异常,那么在 main 函数中会捕获到抛出的异常,但是 p1 指向的动态分配的内存没有被释放,最终会造成内存泄漏

  3. 如果在调用 division(a, b) 函数的过程中抛出了异常,那么在 main 函数中会捕获到抛出的异常,但是 p1 以及 p2 指向的动态分配的内存都没有被释放,最终也会造成内存泄漏


二、智能指针的原理及使用

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术

在对象构造时获取资源,对资源的访问在对象生命周期内始终有效;在对象析构时,即对象生命周期结束时,释放资源

SmartPtr.h

#pragma once
​
namespace yzz
{
    template
    class SmartPtr
    {
    public:
        SmartPtr(T* ptr = nullptr) : _ptr(ptr) 
        { }
​
        ~SmartPtr()
        {
            if (_ptr)
            {
                delete _ptr;
                _ptr = nullptr;
            }
        }
        
        T& operator*()
        {
            return *_ptr;
        }
​
        T* operator->()
        {
            return _ptr;
        }
    private:
        T* _ptr;
    };
}

test.cpp

#include 
#include "SmartPtr.h"
using namespace std;
​
int division(int x, int y)
{
    if (y == 0)
        throw "Division by zero condition!";
    else
        return x / y;
}
​
void func()
{
    yzz::SmartPtr sp1 = new string("hello");
    cout << *sp1 << endl;
​
    yzz::SmartPtr> sp2 = new pair{ "hello", "你好"};
    cout << (*sp2).first << " : " << (*sp2).second << endl;
    cout << sp2->first << " : " << sp2->second << endl;  
    // 为了可读性,编译器将 sp2->->first/second 优化成了 sp2->first/second
​
    int a = 0, b = 0;
    cin >> a >> b;
    cout << division(a, b) << endl;
    // 在 func 函数中,无论有没有异常抛出,
    // func 函数结束后,都会自动调用 sp1 以及 sp2 对象的析构函数释放动态分配的内存,
    // 不再需要手动 delete
}
​
int main()
{
    try {
        func();
    }
    catch (const char* errmsg) {
        cout << errmsg << endl;
    }
    catch (...) {
        cout << "Unknow exception" << endl;
    }
    return 0;
}


三、auto_ptr

C++98/03 标准中提供了 auto_ptr 智能指针

3.1 - 基本使用

#include 
#include 
using namespace std;
​
class A
{
public:
    A(int x = 0) : _i(x)
    {
        cout << "A(int x = 0)" << endl;
    }
​
    ~A()
    {
        cout << "~A()" << endl;
    }
​
    int _i;
};
​
int main()
{
    auto_ptr ap1(new A(1));
    auto_ptr ap2(new A(2));
​
    auto_ptr ap3(ap1);  // 管理权转移
    auto_ptr ap4(new A(4));
    ap4 = ap2;  // 管理权转移
​
    // cout << ap1->_i << endl;  // error
    // cout << ap2->_i << endl;  // error
    cout << ap3->_i << endl;  // 1
    cout << ap4->_i << endl;  // 2
    return 0;
}

【C++ 学习 ㊱】- 智能指针详解_第1张图片

3.2 - 模拟实现

namespace yzz
{
    template
    class auto_ptr
    {
    public:
        auto_ptr(T* ptr = nullptr) : _ptr(ptr)
        { }
​
        ~auto_ptr()
        {
            if (_ptr)
            {
                delete _ptr;
                _ptr = nullptr;
            }
        }
​
        auto_ptr(auto_ptr& ap)
            : _ptr(ap._ptr)
        {
            ap._ptr = nullptr;
        }
​
        auto_ptr& operator=(auto_ptr& ap)
        {
            if (this != &ap)
            {
                if (_ptr)
                    delete _ptr;
​
                _ptr = ap._ptr;
                ap._ptr = nullptr;
            }
            return *this;
        }
​
        T& operator*()
        {
            return *_ptr;
        }
​
        T* operator->()
        {
            return _ptr;
        }
    private:
        T* _ptr;
    };
}


四、unique_ptr

C++11 标准废弃了 auto_ptr,新增了 unique_ptr、shared_ptr 以及 weak_ptr 这三个智能指针

4.1 - 基本使用

#include 
#include 
using namespace std;
​
class A
{
public:
    A(int x = 0) : _i(x)
    {
        cout << "A(int x = 0)" << endl;
    }
​
    ~A()
    {
        cout << "~A()" << endl;
    }
​
    int _i;
};
​
int main()
{
    unique_ptr up1(new A(1));
    unique_ptr up2(new A(2));
​
    // unique_ptr up3(up1);  // error
    unique_ptr up4(new A(4));
    // up4 = up2;  // error
    return 0;
}

4.2 - 模拟实现

从上面的例子中可以看出,unique_ptr 的实现原理就是简单粗暴的防拷贝

namespace yzz
{
    template
    class unique_ptr
    {
    public:
        unique_ptr(T* ptr = nullptr) : _ptr(ptr)
        { }
​
        ~unique_ptr()
        {
            if (_ptr)
            {
                delete _ptr;
                _ptr = nullptr;
            }
        }
​
        unique_ptr(const unique_ptr& up) = delete;
        unique_ptr& operator=(const unique_ptr& up) = delete;
​
        T& operator*()
        {
            return *_ptr;
        }
​
        T* operator->()
        {
            return _ptr;
        }
    private:
        T* _ptr;
    };
}


五、shared_ptr

5.1 - 基本使用

#include 
#include 
using namespace std;
​
class A
{
public:
    A(int x = 0) : _i(x)
    {
        cout << "A(int x = 0)" << endl;
    }
​
    ~A()
    {
        cout << "~A()" << endl;
    }
​
    int _i;
};
​
int main()
{
    shared_ptr sp1(new A(1));
    shared_ptr sp2(new A(2));
    cout << sp1.use_count() << endl;  // 1
    cout << sp2.use_count() << endl;  // 1
    cout << "-------------" << endl;
​
    shared_ptr sp3(sp1);
    cout << sp1.use_count() << endl;  // 2
    cout << sp3.use_count() << endl; // 2
    sp3->_i *= 10;
    cout << sp1->_i << endl;  // 10
    cout << sp3->_i << endl;  // 10
    cout << "-------------" << endl;
​
    shared_ptr sp4(new A(4));
    sp4 = sp2;
    cout << sp2.use_count() << endl;  // 2
    cout << sp4.use_count() << endl; // 2
    sp4->_i *= 10;
    cout << sp2->_i << endl;  // 20
    cout << sp4->_i << endl;  // 20
    return 0;
}

5.2 - 模拟实现

shared_ptr 的实现原理:通过引用计数的方式实现多个 shared_ptr 类对象之间共享资源

引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成 1,每增加一个对象使用该资源,就给计数增加 1,当某个对象被销毁时,先给该计数减 1,然后再检查是否需要释放资源,如果计数为 0,说明该对象是资源的最后一个使用者,于是将资源释放,否则就不能释放,因为还有其他对象在使用该资源

namespace yzz
{
    template
    class shared_ptr
    {
    public:
        shared_ptr(T* ptr = nullptr)
            : _ptr(ptr), _pCnt(new int(1))
        { }
​
        ~shared_ptr()
        {
            if (--(*_pCnt) == 0)
            {
                if (_ptr)
                {
                    delete _ptr;
                    _ptr = nullptr;
                }
                delete _pCnt;
            }
        }
​
        shared_ptr(const shared_ptr& sp)
            : _ptr(sp._ptr), _pCnt(sp._pCnt)
        {
            ++(*_pCnt);
        }
​
        shared_ptr& operator=(const shared_ptr& sp)
        {
            if (_ptr != sp._ptr)
            {
                if (--(*_pCnt) == 0)
                {
                    if (_ptr)
                        delete _ptr;
​
                    delete _pCnt;
                }
​
                _ptr = sp._ptr;
                _pCnt = sp._pCnt;
                ++(*_pCnt);
            }
            return *this;
        }
​
        T& operator*()
        {
            return *_ptr;
        }
​
        T* operator->()
        {
            return _ptr;
        }
​
        int use_count() const
        {
            return *_pCnt;
        }
​
        T* get() const
        {
            return _ptr;
        }
    private:
        T* _ptr;
        int* _pCnt;
    };
}

【C++ 学习 ㊱】- 智能指针详解_第2张图片

 


六、weak_ptr

6.1 - shared_ptr 的循环引用问题

#include 
#include 
using namespace std;
​
class A
{
public:
    A(int x = 0) : _i(x)
    {
        cout << "A(int x = 0)" << endl;
    }
​
    ~A()
    {
        cout << "~A()" << endl;
    }
​
    int _i;
};
​
struct ListNode
{
    A _val;
    shared_ptr _prev;
    shared_ptr _next;
};
​
int main()
{
    shared_ptr sp1(new ListNode);
    shared_ptr sp2(new ListNode);
    // A(int x = 0)
    // A(int x = 0)
    cout << sp1.use_count() << endl;  // 1
    cout << sp2.use_count() << endl;  // 1
​
    sp1->_next = sp2;
    sp2->_prev = sp1;
    cout << sp1.use_count() << endl;  // 2
    cout << sp2.use_count() << endl;  // 2
    // 出现内存泄漏问题
    return 0;
}

【C++ 学习 ㊱】- 智能指针详解_第3张图片

解决方案

#include 
#include 
using namespace std;
​
class A
{
public:
    A(int x = 0) : _i(x)
    {
        cout << "A(int x = 0)" << endl;
    }
​
    ~A()
    {
        cout << "~A()" << endl;
    }
​
    int _i;
};
​
struct ListNode
{
    A _val;
    // weak_ptr 不增加引用计数,并且在可以访问资源的同时,不参与释放资源
    weak_ptr _prev;
    weak_ptr _next;
};
 
int main()
{
    shared_ptr sp1(new ListNode);
    shared_ptr sp2(new ListNode);
    // A(int x = 0)
    // A(int x = 0)
    cout << sp1.use_count() << endl;  // 1
    cout << sp2.use_count() << endl;  // 1
    
    sp1->_next = sp2;
    sp2->_prev = sp1;
    cout << sp1.use_count() << endl;  // 1
    cout << sp2.use_count() << endl;  // 1
    // ~A()
    // ~A()
    return 0;
}

6.2 - 模拟实现

namespace yzz
{
    template
    class weak_ptr
    {
    public:
        weak_ptr() : _ptr(nullptr)
        { }
​
        weak_ptr(const shared_ptr& sp)
            : _ptr(sp.get())
        { }
​
        weak_ptr& operator=(const shared_ptr& sp)
        {
            _ptr = sp.get();
            return *this;
        }
​
        T& operator*()
        {
            return *_ptr;
        }
​
        T* operator->()
        {
            return _ptr;
        }
    private:
        T* _ptr;
    };
}


七、定制删除器

template
struct DeleteArrayFunc
{
    void operator()(T* ptr)
    {
        delete[] ptr;
    }
};
​
template
struct FreeFunc
{
    void operator()(T* ptr)
    {
        free(ptr);
    }
};
​
int main()
{
    shared_ptr sp1(new A(1));  // ok
​
    // shared_ptr sp2(new A[5]);   // error
    shared_ptr sp2(new A[5], DeleteArrayFunc());   // ok
​
    // shared_ptr sp3((A*)malloc(sizeof(A)));  // error
    shared_ptr sp3((A*)malloc(sizeof(A)), FreeFunc());  // ok
​
    // shared_ptr sp4(fopen("test.cpp", "r"));  // error
    shared_ptr sp4(fopen("test.cpp", "r"), 
                         [](FILE* fp) { fclose(fp); });  // error
    return 0;
}

shared_ptr 的模拟实现

#include 
​
namespace yzz
{
    template
    class shared_ptr
    {
    public:
        shared_ptr(T* ptr = nullptr)
            : _ptr(ptr), _pCnt(new int(1)), _del([](T* ptr) { delete ptr; })
        { }
​
        template
        shared_ptr(T* ptr, D del)
            : _ptr(ptr), _pCnt(new int(1)), _del(del)
        { }
​
        ~shared_ptr()
        {
            if (--(*_pCnt) == 0)
            {
                if (_ptr)
                {
                    _del(_ptr);
                    _ptr = nullptr;
                }
                delete _pCnt;
            }
        }
        
        // ... ...
    private:
        T* _ptr;
        int* _pCnt;
        std::function _del;
    };
}

你可能感兴趣的:(C++,c++,学习,开发语言)