C++智能指针详解

本文深入讲解了智能指针的概念及应用场景,包括shared_ptr、unique_ptr和weak_ptr的使用方法及注意事项,帮助读者理解如何有效避免内存泄漏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.前言

首先我们引入智能指针之前要先了解一个概念——内存泄漏,那什么是内存泄漏呢?有什么危害呢?我们该怎么去解决呢?

1.1什么是内存泄漏

由于堆区是由程序员进行分配和释放,我们用new或者malloc申请一段空间之后,没有调用delete或者free,导致这部分申请的空间没有被释放,就像我们买了个房子,但是不记得房子的地址,我们也没办法入住使用这个空间,这个空间只能白白放着没法利用,这样就造成了资源浪费。

1.2内存泄漏的危害

如果申请的内存没有正确的进行释放,那么随着程序的正常结束,其实这个内存也会被正确释放。

但是,如果一个长期运行的程序,如服务器等遇到内存泄漏的问题,危害就非常大,而且会让整个变得不稳定,带来的危害就是导致我们可用的内存越来越少,导致一些服务的失效(打开文件,创建套接字等)

1.3如何解决内存泄漏

首先我们不妨想想:为什么内存泄漏常常发生在堆区上,而栈区并不会发生内存泄漏,正是应为栈区是系统自动分配和释放的,而堆区是程序员进行维护的,那么如果我们也提升指针的智能性?让我们的操作尽量简化,这时我们就可以采用智能指针。让系统来帮我们做一部分工作。

2.智能指针

2.1智能指针前言

我们知道c++中不像java中那样自带垃圾回收的机制,必须得手动释放我们在堆区创建的内存,否则就会造成内存泄漏。因此c++中引入了智能指针,最开始时在C++98提供的智能指针为auto_ptr,以帮助我们自动进行内存的释放,但是随着接下来的编程体验,我们在STL容器中,需要进行更精细的操作,所以我们在c++11中摒弃了auto_ptr。在头文件<memory>中

引入了三个智能指针:

  • std::shared_ptr:共享的智能指针
  • std::unique_ptr:独占的智能指针
  • std::weak_ptr:弱引用的智能指针,它既不共享指针,也不进行操作资源,是用来和shared_ptr一起使用,解决循环引用的问题。

2.2shared_ptr

2.2.1shared_ptr的原理

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

  • 在shared_ptr内部,为它管理的资源维护了一份引用计数,用来记录该资源被几个对象所共同管理
  • 在引用的对象销毁(调用析构函数)时,就说明自己不会使用该资源了,对象的引用计数减一
  • 如果引用计数减到0,就说明当前自己已经是最后一次使用这个资源的对象,所以此时必须释放该资源
  • 如果不是0,那么也就说明还有其他对象在管理这份资源,此时只需要将引用计数减一即可,不需要释放资源

2.2.2shared_ptr的初始化

共享智能指针指我们可以有同时多个智能指针同时管理一块有效的内存,shared_ptr是一个模板类,有三种初始化方法如下:

  • 通过基本的构造函数进行初始化
  • std::make_shared辅助函数进行初始化
  • reset方法

共享智能指针对象初始化完毕之后就指向了要管理的那块堆区内存,如何想要查看当前多少个智能指针在同时管理(使用)这块堆区内存,就可以使用共享智能指针提供的一个成员函数use_count.

构造函数初始化:

void main()
{
	//使用智能指针管理了一块int型的堆内存
	shared_ptr<int> ptr1(new int(555));
	//使用智能指针管理了一块字符数组对应的堆内存
	shared_ptr<char> ptr2(new char[520]);
	//只创建智能指针对象
	shared_ptr<int> ptr3;
	//创建智能指针对象,并且初始化为空
	shared_ptr<int> ptr4(nullptr);
	cout << "ptr1管理的内存引用计数为" << ptr1.use_count() << endl;
	cout << "ptr2管理的内存引用计数为" << ptr2.use_count() << endl;
	cout << "ptr3管理的内存引用计数为" << ptr3.use_count() << endl;
	cout << "ptr4管理的内存引用计数为" << ptr4.use_count() << endl;
}

打印结果为:

如果智能指针初始化了一块有效的堆区内存,那么这块内存的引用计数+1,如果智能指针没有被初始化或者被初始化为nullptr空指针,引用计数为,另外,不要使用一个原始指针初始化多个shared_ptr

void main()
{
	int *a = new int;
	shared_ptr<int> ptr1(a);
	shared_ptr<int> ptr2(a);
	cout << "ptr1的引用计数为" << ptr1.use_count() << endl;
	cout << "ptr2的引用计数为" << ptr1.use_count() << endl;
    //报错,因为ptr1和ptr2同时指向了同一块内存,会被析构两次,引起内存泄漏
}

 

拷贝和移动构造函数初始化

如果智能指针被初始化之后,就可以通过这个智能指针初始化其他新对象。在创建对象的时候,对应的拷贝构造函数或者移动构造函数就被调用了

void main()
{
	shared_ptr<int> ptr1(new int(520));
	cout << "ptr1的引用计数为" << ptr1.use_count() << endl;

	//拷贝构造函数
	shared_ptr<int> ptr2(ptr1);
	cout << "ptr2的引用计数为" << ptr2.use_count() << endl;
	shared_ptr<int> ptr3 = ptr2;
	cout << "ptr3的引用计数为" << ptr3.use_count() << endl;

	//移动构造函数
	shared_ptr<int> ptr4(std::move(ptr1));
	cout << "ptr4的引用计数为" << ptr4.use_count() << endl;
	shared_ptr<int> ptr5 = std::move(ptr2);
	cout << "ptr5的引用计数为" << ptr5.use_count() << endl;
}

打印结果如下:

因为ptr4和ptr5为移动构造,并没有产生新的shared_ptr指针,所以引用计数没有发生

如果我们在刚刚的程序底部打印下ptr1的引用计数我们会发现ptr1的引用计数为0。

std::make_shared初始化

通过c++11所提供的std::make_shared()就可以完成内存对象的创建,并将其初始化给智能指针。

int main()
{
	// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
	shared_ptr<int> ptr1 = make_shared<int>(520);
	cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;

	shared_ptr<Test> ptr2 = make_shared<Test>();
	cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;

	shared_ptr<Test> ptr3 = make_shared<Test>(520);
	cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;

	shared_ptr<Test> ptr4 = make_shared<Test>("QQQQ");
	cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
	ptr4.reset();
	//我们将ptr4的引用计数重置,此时会直接释放ptr4指向的内存,直接调用析构函数
	cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
	return 0;
}
ptr1管理的内存引用计数: 1
	无参构造函数
	ptr2管理的内存引用计数 : 1
	int类型构造函数 520
	ptr3管理的内存引用计数 : 1
	string类型的构造函数QQQQ
	ptr4管理的内存引用计数 : 1
	析构函数
	ptr4管理的内存引用计数 : 0
	析构函数
    析构函数

我们可以看到ptr4调用reset之后,指向的内存直接被析构,而ptr3和ptr2随着主函数的调用完毕被自动销毁,同样自动调用了析构函数。

如果使用拷贝的方式初始化共享智能指针,这两个对象会同时管理同一块内存,堆内存对应的引用技术也会增加。如果使用移动构造的方式初始化智能指针对象,只是转让了内存的所有权,管理内存的对象不会增加,因此内存引用技术不会增加。

reset方法初始化

int main()
{
	// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
	shared_ptr<int> ptr1 = make_shared<int>(520);
	shared_ptr<int> ptr2 = ptr1;
	shared_ptr<int> ptr3 = ptr1;
	shared_ptr<int> ptr4 = ptr1;
	cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
	cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
	cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
	cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

	ptr4.reset();
	cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
	cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
	cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
	cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

	shared_ptr<int> ptr5;
	ptr5.reset(new int(250));
	cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;
	return 0;
}
打印结果如下:
	ptr1管理的内存引用计数 : 4
	ptr2管理的内存引用计数 : 4
	ptr3管理的内存引用计数 : 4
	ptr4管理的内存引用计数 : 4

	ptr1管理的内存引用计数 : 3
	ptr2管理的内存引用计数 : 3
	ptr3管理的内存引用计数 : 3
	ptr4管理的内存引用计数 : 0

	ptr5管理的内存引用计数 : 1

对于一个未初始化的共享智能指针,可以通过reset方法来初始化,当智能指针中有值得时候,调用reset会使引用计数减1.

class Test
{
public:
	Test()
	{
		cout << "无参构造函数" << endl;
	}
	Test(int x)
	{
		cout << "int类型构造函数 " << x << endl;
	}
	Test(string str)
	{
		cout << "string类型的构造函数" << str << endl;
	}
	~Test()
	{
		cout << "析构函数" << endl;
	}
};

int main()
{
	shared_ptr<Test> ptr1(new Test(666));
	cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
	ptr1.reset(new Test("666"));
	cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;

	return 0;
}
打印结果如下:
	int类型构造函数 666
	ptr1管理的内存引用计数: 1
	string类型的构造函数666
	析构函数
	ptr1管理的内存引用计数 : 1
	析构函数

我们可以看到,如果ptr在调用reset之前指向了一块空间,会先将引用计数减一,然后指向新的空间

获取原始指针

get()函数

int main()
{
	shared_ptr<int>  p(new int);
	*p = 100;
	cout << p.get() << "  " << p << endl;
	return 0;
}
00D4EF80  00D4EF80

我们可以看到get方法返回的指针和原始指针指向的是同一个逻辑地址。

2.3unique_ptr(独占智能指针)

初始化

std::unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针,但是不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。

#include <iostream>
#include <string>
#include <memory>
using namespace std;

int main()
{
	// 通过构造函数初始化对象
	unique_ptr<int> ptr1(new int(10));
	//  报错
	unique_ptr<int> ptr2 = ptr1;
	return 0;
}

unique_ptr不允许被复制,但是可以通过函数返回给其他的unique_ptr,还可以通过std::move()转移给其他的unique_ptr。还是一个unique_ptr独占一个地址。

使用 reset 方法可以让 unique_ptr 解除对原始内存的管理,也可以用来初始化一个独占的智能指针。

#include <iostream>
#include <memory>
using namespace std;
int main()
{
	unique_ptr<int> ptr1(new int(10));
	unique_ptr<int> ptr2 = move(ptr1);

	ptr2.reset(new int(250));
	cout << *ptr2.get() << endl;	// 得到内存地址中存储的实际数值 250

	return 0;
}

    250

2.4weak_ptr(弱引用智能指针)

弱引用智能指针 std::weak_ptr 可以看做是 shared_ptr 的助手,它不管理 shared_ptr 内部的指针。std::weak_ptr 没有重载操作符 * 和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视 shared_ptr 中管理的资源是否存在。

初始化

#include <iostream>
#include <memory>
using namespace std;

int main()
{
	shared_ptr<int> sp(new int);

        //weak_ptr<int> wp1; 构造了一个空 weak_ptr 对象
	weak_ptr<int> wp1; 

        // weak_ptr<int> wp2(wp1); 通过一个空 weak_ptr 对象构造了另一个空 weak_ptr 对象
        weak_ptr<int> wp2(wp1);
        
        //weak_ptr<int> wp3(sp); 通过一个 shared_ptr 对象构造了一个可用的 weak_ptr 实例对象
	weak_ptr<int> wp3(sp);

        //wp4 = sp; 通过一个 shared_ptr 对象构造了一个可用的 weak_ptr 实例对象(这是一个隐式类型转换)
	weak_ptr<int> wp4;
	wp4 = sp;

        //wp5 = wp3; 通过一个 weak_ptr 对象构造了一个可用的 weak_ptr 实例对象
	weak_ptr<int> wp5;
	wp5 = wp3;

	return 0;
}

use_count()

通过调用 std::weak_ptr 类提供的 use_count() 方法可以获得当前所观测资源的引用计数

int main()
{
	shared_ptr<int> sp(new int);

	weak_ptr<int> wp1;
	weak_ptr<int> wp2(wp1);
	weak_ptr<int> wp3(sp);
	weak_ptr<int> wp4;
	wp4 = sp;
	weak_ptr<int> wp5;
	wp5 = wp3;

	cout << "use_count: " << endl;
	cout << "wp1: " << wp1.use_count() << endl;
	cout << "wp2: " << wp2.use_count() << endl;
	cout << "wp3: " << wp3.use_count() << endl;
	cout << "wp4: " << wp4.use_count() << endl;
	cout << "wp5: " << wp5.use_count() << endl;
	shared_ptr<int> sp1 = sp;
	cout << "use_count: " << endl;
	cout << "wp1: " << wp1.use_count() << endl;
	cout << "wp2: " << wp2.use_count() << endl;
	cout << "wp3: " << wp3.use_count() << endl;
	cout << "wp4: " << wp4.use_count() << endl;
	cout << "wp5: " << wp5.use_count() << endl;
	return 0;
}
use_count:
wp1: 0
	wp2 : 0
	wp3 : 1
	wp4 : 1
	wp5 : 1
	use_count :
	wp1 : 0
	wp2 : 0
	wp3 : 2
	wp4 : 2
	wp5 : 2

通过打印的结果可以知道,虽然弱引用智能指针 wp3、wp4、wp5 监测的资源是同一个,但是它的引用计数并没有发生任何的变化,也进一步证明了 weak_ptr只是监测资源,并不管理资源。而且当weak_ptr指向的shared_ptr的引用计数发生改变时,weak_ptr也要做出改变

expired()

通过调用 std::weak_ptr 类提供的 expired() 方法来判断观测的资源是否已经被释放

int main()
{
	shared_ptr<int> shared(new int(10));
	weak_ptr<int> weak(shared);
	cout << "1. weak " << (weak.expired() ? "被" : "没被") << " 释放" << endl;
	cout <<typeid(weak.expired()).name()<<"  "<<weak.expired()<<endl;
	shared.reset();
	cout << "2. weak " << (weak.expired() ? "被" : "没被") << " 释放" << endl;

	return 0;
}
1. weak 没被 释放
bool  0
2. weak 被 释放

weak_ptr 监测的就是 shared_ptr 管理的资源,当共享智能指针调用 shared.reset(); 之后管理的资源被释放,因此 weak.expired() 函数的结果返回 true,表示监测的资源已经不存在了。

lock()

通过调用 std::weak_ptr 类提供的 lock() 方法来获取管理所监测资源的 shared_ptr 对象

int main()
{
	shared_ptr<int> sp1, sp2;
	weak_ptr<int> wp;

	sp1 = std::make_shared<int>(520);
	wp = sp1;
	sp2 = wp.lock();
	cout << "use_count: " << wp.use_count() << endl;

	sp1.reset();
	cout << "use_count: " << wp.use_count() << endl;

	sp1 = wp.lock();
	cout << "use_count: " << wp.use_count() << endl;

	cout << "*sp1: " << *sp1 << endl;
	cout << "*sp2: " << *sp2 << endl;

	return 0;
}

use_count: 2
use_count: 1
use_count: 2
*sp1: 520
*sp2: 520

sp2 = wp.lock(); 通过调用 lock() 方法得到一个用于管理 weak_ptr 对象所监测的资源的共享智能指针对象,使用这个对象初始化 sp2,此时所监测资源的引用计数为 2.

sp1.reset(); 共享智能指针 sp1 被重置,weak_ptr 对象所监测的资源的引用计数减 1.

sp1 = wp.lock(); sp1 重新被初始化,并且管理的还是 weak_ptr 对象所监测的资源,因此引用计数加 1.

共享智能指针对象 sp1 和 sp2 管理的是同一块内存,因此最终打印的内存中的结果是相同的,都是 520.

reset()

通过调用 std::weak_ptr 类提供的 reset() 方法来清空对象,使其不监测任何资源。

int main()
{
	shared_ptr<int> sp(new int(10));
	weak_ptr<int> wp(sp);
	cout << "1. wp " << (wp.expired() ? "被" : "没被") << " 释放" << endl;

	wp.reset();
	cout << "2. wp " << (wp.expired() ?  "被" : "没被") << " 释放" << endl;

	return 0;
}

1. wp is 没被 释放
2. wp is 被 释放

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值