C++ 智能指针

0. 内存泄漏

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

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

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

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

智能指针的使用及原理

1. RAII

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

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

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

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

// 智能指针(包括接下来会介绍到的 unique_ptr、shared_ptr)
unque_ptr<int[]> pb(new int[10]);
// 智能指针对象析构时,会自动释放资源,不需要显式 delete
  1. 对象所需的资源在其生命周期内始终有效
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
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
private:
	T* _ptr;
};
  • 重载 operator*() operator->()
  public:
  	T& operator*()
      {
          return *_ptr;
      }
  
      T* operator->()
      {
          return _ptr;
      }
3. unique_ptr
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 释放资源。

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 所管理对象的生命周期,可以很好解决循环引用的问题。

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;
};
  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值