C++编程(一)—— 智能指针

本文介绍了C++中智能指针的概念,旨在解决裸指针可能导致的内存泄露问题。从裸指针出发,讨论了手写简易智能指针的挑战,接着详细阐述了不带引用计数的智能指针,如auto_ptr、scoped_ptr和unique_ptr,以及它们各自的特点。然后重点讲解了带引用计数的智能指针,包括shared_ptr和weak_ptr,分析了引用计数的增减原理和循环引用问题,并提出了解决方案。最后提到了多线程环境下智能指针对共享对象的线程安全问题。
摘要由CSDN通过智能技术生成


裸指针的问题

new出的内存如果不进行释放,会导致内存泄露。

手写简易 智能指针

目的:保证能做到资源的自动释放

最简单的智能指针

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T *ptr = nullptr) : mptr(ptr) {}
	~CSmartPtr()
	{
		delete mptr;
		mptr = nullptr;
	}
	T& operator*() { return *mptr; }
	T* operator->() { return mptr; }
private:
	T* mptr;
}

使用:

class Test
{
public:
	void test() {cout << "call Test::test() << endl; }
};
int main()
{
	CSmartPtr<int> ptr1(new int);
	*ptr1 = 20;
	
	CSmartPtr<Test> ptr2(new Test());
	ptr2->test();  // 等同于 (*ptr2).test()
	
}

但是随之而来的问题是,如何让两个指针一起管理同一块内存区域?

CSmartPtr<int> p1(new int);
CSmartPtr<int> p2(p1);

如果做的引用浅拷贝,在析构时指针double free。

如果做的是深拷贝:

CSmartPtr(const CSmartPtr<T> &src)  // 深拷贝
{
	mptr = new T(*src.mptr);
}

那么这两块指针会指向两块不同的内存空间。但是用户是想让p1和p2指针管理同一块资源,这样也是错的。所以深拷贝根本不行。

不带引用计数的智能指针

首先重写不带引用计数的智能指针。不带引用计数的智能指针有auto_ptr(C++不推荐使用),scoped_pte(C++11使用的很少),unique_ptr(C++11)。
不带引用计数的智能指针解决不了浅拷贝问题

auto_ptr

auto_ptr:浅拷贝时,p1赋nullptr (源码中的release方法)

auto_ptr<int> p1(new int);
auto_ptr<int> p2(p1);

auto_ptr底层是只有最后一个指针可以使用,其他所有指针全部置nullptr。
像上面的例子,只有p2能用,p1已经nullptr了。

scoped_pte

浅拷贝时,报错,直接将拷贝构造删除了。

scoped_ptr(const scoped_ptr<T>&) = delete;
scoped_ptr<T>& operator=(const scoped_ptr<T>&) = delete;

unique_ptr

独占指针,只让一个指针管理资源。
虽然unique_ptr也将拷贝构造删除了,但是unique_ptr提供了移动构造

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

unique_ptr(unique_ptr<T> &&src)
unique_ptr<T>& operator=(unique_ptr<T> &&src)
unique_ptr<int> p1(new int);
unique_ptr<int> p2(std::move(p1)); // 得到当前变量的右值引用

这时p1已经不持有资源了(move了)

带引用计数的智能指针

shared_ptr, weak_ptr

带引用计数好处:多个智能指针可以管理同一个资源。

带引用计数的含义:给每一个对象资源,匹配一个引用计数
智能指针 =》 资源的时候 =》 引用计数+1
智能指针 =》不使用资源的时候 =》 引用计数-1 =》 != 0
当引用计数为0时资源释放了

手写带引用计数的智能指针CSmartPtr

template<typename T>
class RefCnt
{
public:
	Refcnt(T *ptr = nullptr) : mptr(ptr)
	{
		if(mptr != nullptr)
		{
			mcount = 1;
		}
	}
	void addRef() { mcount++; }  //添加资源的引用计数
	void delRef() { --mcount; }
private:
	T *mptr;
	int mcount;
}

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T *ptr = nullptr) : mptr(ptr) 
	{
		mpRefCnt = new RefCnt<T>(mptr);
	}
	~CSmartPtr()
	{
		if(0 == mpRefCnt->delRef())
		{
			delete mptr;
			mptr = nullptr;
		}
	}
	CSmartPtr(const CSmartPtr& src) : mptr(ptr), mpRefCnt(src.mpRefCnt)
	{
		if(mptr != nullptr)
			mpRefCnt->addRef();
	}
	CSmartPtr<T>& operate=(const CSmartPtr& src)
	{
		if(this == &src)
			return *this;   // 防止自赋值ptr3 = ptr3;
		
		if(0 == mpRefCnt->delRef())
		{
			delete mptr;
		}
		mptr = src.mptr;
		mpRefCnt = src.mpRefCnt;
		mpRefCnt->addRef();
		return *this;
	}
	T& operator*() { return *mptr; }
	T* operator->() { return mptr; }
private:
	T* mptr;   // 指向资源的指针
	RefCnt<T> *mpRefCnt;   // 指向该资源引用计数对象的指针
}
int main()
{
	CSmartPtr<int> ptr1(new int);
	CSmartPtr<int> ptr2(ptr1);
	CSmartPtr<int> ptr3;
	ptr3 = ptr2;
	
	*ptr1 = 20;  // 这时指针1、2、3都是20
}

这个自己实现的CSmartPtr并不是线程安全的,因为设计到计数值的加减。
注意:
shared_ptr引用计数在什么情况下会计数减一?答:在赋值和析构的情况都会减一。

shared_ptr

强智能指针:可以改变资源的引用计数。

强智能指针循环引用问题(交叉引用):

问题主要内容:指针的引用计数永远不会减为零,对象也永远不会被销毁,造成new出来的资源无法释放,内存泄露。
示例代码:

#include <iostream>
#include <memory>

class B; // 前置声明,用于在类A中声明B的智能指针

class A {
public:
    std::shared_ptr<B> b_ptr;

    A() {
        std::cout << "A constructor" << std::endl;
    }

    ~A() {
        std::cout << "A destructor" << std::endl;
    }
};

class B {
public:
    std::shared_ptr<A> a_ptr;

    B() {
        std::cout << "B constructor" << std::endl;
    }

    ~B() {
        std::cout << "B destructor" << std::endl;
    }
};

int main() {
    std::shared_ptr<A> a_ptr = std::make_shared<A>();
    std::shared_ptr<B> b_ptr = std::make_shared<B>();

    a_ptr->b_ptr = b_ptr;
    b_ptr->a_ptr = a_ptr; // 循环引用,导致内存泄漏

    return 0;
}

问题描述:
两个类 A 和 B。类 A 包含一个指向类 B 的智能指针 b_ptr,而类 B 包含一个指向类 A 的智能指针 a_ptr。
在 main 函数中,我们创建了两个对象 a_ptr 和 b_ptr,然后将它们互相连接形成循环引用
在这里插入图片描述
解决方案:
定义对象的时候用强智能指针,引用对象的时候用弱智能指针
在这里插入图片描述

weak_ptr

弱智能指针:

  1. 不能改变资源的引用计数。
  2. 他只负责监控指向的内存,只是一个观察者,没有裸指针的.->功能。
    weak_ptr 观察 shared_ptr,shared_ptr再管理资源(内存)

但是弱智能指针可以提升为强智能指针(可能会失败)

shared_ptr<A> ps = _ptra.lock();
if(ps != nullptr)  ps->testA();

多线程访问共享对象的线程安全问题

子线程

// 子线程
void handler01(weak_ptr<A> pw)
{
	std::this thread::sleep for(std::chrono::seconds(2));
	shared_ptr<A> sp = pw.lock();   // 升级!
	if (sp != nullptr)  sp->testA();   // 看升级结果,侦测A对象是否存活(p已经出作用域了)
	else cout << "A对象已经析构,不能再访问!<< endl;
}

main线程


int main()
{
	{
		shared ptr<A> p(new A());
		thread tl(handler01, weak ptr<A>(p));
		t1.detach();
	}
	std::this thread::sleep for(std::chrono::seconds(20));
	// 阻塞等待子线程结束
	// t1.join();
	return 0;
}

此时结构为:A对象已经析构,不能再访问!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋雨qy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值