C++ 智能指针

0. 内存泄漏

内存泄露 :因为错误或疏忽,造成程序未能释放某段不再使用的内存的情景。

内存泄漏的危害长期运行的程序出现内存泄露,如:操作系统等,会导致响应越来越慢,最终卡死。

在正式了解之前,需要明晰一个观念,否则容易把 智能指针的作用自定义类型生命周期结束时会自动调用析构函数 混淆:

智能指针的出现,是为了解决传统指针内存泄露的问题

智能指针的使用及原理

1. RAII

RAII(Resource Acquisition Is Initialization):资源获取即初始化利用对象生命周期来控制资源

  • 在构造对象时自动获取资源
  • 在对象析构时自动释放资源

实际上,我们把管理一份资源的责任委托给一个对象,这样做有两个好处:

  1. 不需要显式释放资源
cpp 复制代码
// 传统指针
int* pa = new int[10];
...
delete[] pa;

// 智能指针(包括接下来会介绍到的 unique_ptr、shared_ptr)
unque_ptr<int[]> pb(new int[10]);
// 智能指针对象析构时,会自动释放资源,不需要显式 delete
  1. 对象所需的资源在其生命周期内始终有效
cpp 复制代码
class MyClass {
public:
	MyClass(int data) : _data(data) {
		std::cout << "MyClass constructed with data: " << _data << std::endl;
	}

	~MyClass() {
		std::cout << "MyClass destructed." << std::endl;
	}

	void PrintData() const {
		std::cout << "Data: " << _data << std::endl;
	}

private:
	int _data;
};

int main() {
	// 使用 std::unique_ptr 创建一个 MyClass 对象
	std::unique_ptr<MyClass> my_instance = std::make_unique<MyClass>(42);

	// 可以像普通指针一样使用 my_instance
	my_instance->PrintData();

	// 当 my_instance 超出作用域时,其析构函数会自动调用,释放 MyClass 实例
	// 无需手动 delete
	return 0;
}
2. 智能指针的原理
  • RAII
cpp 复制代码
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
private:
	T* _ptr;
};
  • 重载 operator*() operator->()
cpp 复制代码
  public:
  	T& operator*()
      {
          return *_ptr;
      }
  
      T* operator->()
      {
          return _ptr;
      }
3. unique_ptr
cpp 复制代码
template<class T>
class unique_ptr
{
public:
    unique_ptr(T* ptr)
        :_ptr(ptr)
    {}

    ~unique_ptr()
    {
        if (_ptr)
        {
            cout << "~unique_ptr()" << endl;
            delete _ptr;
            _ptr = nullptr;
        }
    }
	
    // 禁止拷贝
    unique_ptr(unique_ptr<T>&) = delete;
    unique_ptr<T>& operator=(unique_ptr<T>&) = delete;

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

    T* operator->()
    {
        return _ptr;
    }
private:
    T* _ptr;
};
4. shared_ptr shared_ptr

在 unique_ptr 基础上加入了引用计数,解决拷贝导致资源管理权转移的问题 ------ 一份资源对应一个引用计数;

引用计数 代表着 对应的资源被几个 shared_ptr 所共有;

在对象被销毁(析构)时,--(*_psize)

*_psize == 0 时,delete _ptr 释放资源。

cpp 复制代码
template<class T>
class shared_ptr
{
public:
    shared_ptr(T* ptr)
        :_ptr(ptr)
        ,_psize(new int(1))
    {}

    void release()
    {
        if (--(*_psize) == 0)
        {
            if (_ptr)
            {
                cout << "delete: " << _ptr << endl;
                delete _ptr;
                _ptr = nullptr;
            }
            delete _psize; // _psize 是在堆上开空间,需要 delete
        }
    }

    ~shared_ptr()
    {
        release();
    }

    shared_ptr(shared_ptr<T>& sp)
        :_ptr(sp._ptr)
        ,_psize(sp._psize)
    {
        ++(*_psize);
    }

    int use_count()
    {
        return *_psize;
    }

    shared_ptr<T>& operator=(shared_ptr<T>& sp)
    {
        if (_ptr != sp._ptr)
        {
            release();

            _ptr = sp._ptr;
            _psize = sp._psize;
            ++(*_psize);
        }
        return *this;
    }

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

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

    T* get()
    {
        return _ptr;
    }

private:
    T* _ptr;
    // 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源
    int* _psize;
};

shared_ptr 同样有设计缺陷:循环引用

if (--(*_psize) == 0) 才真正释放资源,循环引用 会导致资源无法被释放:

左边节点的释放,依赖于 delete 右边节点的 _prev;

delete 右边节点的 _prev,依赖于 右边节点的释放;

右边节点的释放,依赖于 delete 左边节点的 _next;

delete 左边节点的 _next,依赖于 左边节点的释放 ... ... (如此循环下去,两个节点的资源都无法被释放)

因此,需要引入 weak_ptr 。

weak_ptr 不会增加它所指向对象的引用计数,它不会影响 shared_ptr 所管理对象的生命周期,可以很好解决循环引用的问题。

cpp 复制代码
template<class T>
class weak_ptr
{
public:
    weak_ptr(T* ptr)
        :_ptr(ptr)
    {}

    weak_ptr(shared_ptr<T>& sp)
    {
        _ptr = sp.get();
    }

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

    T* operator->()
    {
        return _ptr;
    }
private:
    T* _ptr;
};
相关推荐
南猿北者18 分钟前
Docker Volume
运维·docker·容器
网络研究院2 小时前
Am I Isolated:一款安全态势基准测试工具
容器·工具·基准测试·安全态势
懒惰的bit2 小时前
基础网络安全知识
学习·web安全·1024程序员节
矛取矛求3 小时前
Linux如何更优质调节系统性能
linux
李元豪3 小时前
【智鹿空间】c++实现了一个简单的链表数据结构 MyList,其中包含基本的 Get 和 Modify 操作,
数据结构·c++·链表
UestcXiye4 小时前
《TCP/IP网络编程》学习笔记 | Chapter 9:套接字的多种可选项
c++·计算机网络·ip·tcp
内核程序员kevin4 小时前
在Linux环境下使用Docker打包和发布.NET程序并配合MySQL部署
linux·mysql·docker·.net
kayotin4 小时前
Wordpress博客配置2024
linux·mysql·docker
一丝晨光5 小时前
编译器、IDE对C/C++新标准的支持
c语言·开发语言·c++·ide·msvc·visual studio·gcc
丶Darling.5 小时前
Day40 | 动态规划 :完全背包应用 组合总和IV(类比爬楼梯)
c++·算法·动态规划·记忆化搜索·回溯