帮你搞懂C++智能指针

为什么使用智能指针

使用C++的朋友都知道,C++中的内存泄漏是个很难解决的问题。

产生内存泄漏的几种场景:

  1. 由于程序员的疏忽,导致类的的析构函数中没有对所有或者部分使用的内存做回收
  2. 随着调用层级的增多,所有权转移后,回收资源的动作没法确定谁负责回收,会变得很难
  3. 在出现抛出异常的时候,内存没法回收

为了解决这个问题,由c++之父Bjarne Stroustrup提出了RAII机制

RAII机制

RAII(Resource Acquisition Is Initialization),中文翻译为资源获取即初始化,Bjarne Stroustrup 说:使用局部对象来管理资源的技术称为资源获取即初始化;

这里的资源主要是指操作系统中有限的东西如内存、网络套接字等等,局部对象是指存储在栈的对象,它的生命周期由编译器管理,无需人工介入。

RAII的做法

是使用一个对象,在其构造时获取对应的资源,在对象生命期内控制对资源的访问,使之始终保持有效,最后在对象析构的时候,释放构造时获取的资源。当定义的局部变量的生命结束时,它的析构函数就会自动的被调用,如此,就不用程序员显示的去调用释放资源的操作了。

智能指针

C++11 引入了智能指针的概念,智能指针的本质是一个对象,是一个行为表现都像指针的对象。它的封装利用了RAII机制或者思想,是C++语言的一种管理资源、避免泄漏的惯用法。

智能指针拥有以下特性:

  1. 拥有RAII的机制来管理指针。对于编译器来说,智能指针实际上是一个栈对象,并非指针类型,在栈对象生命期即将结束时,智能指针通过析构函数释放它管理的堆内存
  2. 有指针的基本功能。所有智能指针都重载了“*”和“->”操作符,让它可以直接返回对象的引用以及能用->去操作其指向的对象。若要访问智能指针原来的方法则使用“·”操作符。
  3. 它还需要考虑深浅拷贝问题

引用计数

引用计数这种计数是为了防止内存泄露而产生的。 基本想法是对于动态分配的对象,进行引用计数,每当增加一次对同一个对象的引用,那么引用对象的引用计数就会增加一次, 每删除一次引用,引用计数就会减一,当一个对象的引用计数减为零时,就自动删除指向的堆内存。

智能指针可分为两部分,一个是原指针,一个是引用计数(关联的计数器)。创建一个智能指针的时候引用计数为1,当引用计数为0的时候,智能指针本身会自动释放。当该智能指针被其他指针所用,引用计数就会相应的叠加,可以这么理解:引用计数的大小就是当前管理该内存的指针的数量。

智能指针包括 std::shared_ptr/std::unique_ptr/std::weak_ptr,使用它们需要包含头文件 。

std::unique_ptr(独占指针)

unique_ptr 是效率最接近普通指针的智能指针,推荐在一切可能得地方都使用
unique_ptr 在默认情况下和裸指针的大小是一样的。
所以 内存上没有任何的额外消耗,性能是最优的

std::unique_ptr 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全。
既然是独占,就是不可复制。但是,我们可以利用 std::move 将其转移给其他的 unique_ptr

unique_ptr 的使用

#include<iostream>
#include<memory>
int main()
{
	//std::unique_ptr<int> u(new int(10));//绑定申请的堆区资源
	std::unique_ptr<int> u = std::make_unique<int>(10); // 推荐使用 make_unique (从 C++14 引入)
	std::unique_ptr<int>u2(std::move(u));//转移所有权到u2(移动语义) 
	
	//不允许同一份资源,被多个unique_Ptr管理
	//std::unique_ptr<int>u2(u);不能拷贝
	//std::unique_ptr<int>u3=u;不能赋值

	auto p=u.release();//释放所有权 返回原指针,相当于unique_ptr不参与原指针的管理了
	u.reset(new int(20));//重新制定所有权
	std::shared_ptr s(std::move(u2));//在有需要的时候,我们也可以将它转移到shared_ptr中管理。

}

std::shared_ptr(共享指针)

在使用 shared_ptr 之前应该考虑,是否真的需要使用 shared_ptr, 而非 unique_ptr。
shared_ptr 代表的是共享所有权,即多个 shared_ptr 可以共享同一块内存。

shared_ptr 内部是利用引用计数来实现内存的自动管理,每当复制一个 shared_ptr,引用计数会 + 1。当一个 shared_ptr 离开作用域时,引用计数会 - 1。当引用计数为 0 的时候,则 delete 内存。

初始化和赋值(注意不要用一个原始指针初始化多个shared_ptr 会导致重复释放内存)

#include <memory>
#include<iostream>
int main() {
	int* a = new int(10);
	std::shared_ptr<int>s1(a);
	std::shared_ptr<int>s2(new int(10));
	std::shared_ptr<int>s3 = s1;
	std::shared_ptr<int>s4=make_shared<int>(30);
	auto s5 = std::make_shared<int>(20);	
	return 0;
}

shared_ptr其他成员函数

#include <memory>
#include<iostream>
#include<algorithm>
class Person
{
public:
	Person(int a) :a(a){}
public:
	int a;
};
int main() {
	std::shared_ptr<Person>s(new Person(2));
	std::cout << (*s).a << std::endl;
	std::cout << s->a << std::endl;

	std::cout << s.use_count() << std::endl;//得到引用计数的数量即当前管理该内存的指针的个数
	Person*p = s.get();//得到原始的指针

	std::shared_ptr<Person>s2 = s;
	std::cout << s.use_count() << std::endl;
	s.~shared_ptr();//析构函数它的作用是use_count()--然后其检查是否为0 若为0就释放,若不为0选择无视
	std::cout << s2.use_count() << std::endl;//此处打印为1说明上面析构函数没有释放内存资源
	
	std::shared_ptr<Person>s3(new Person(4));
	s3.swap(s2);//交换管理的内存,交换指针
	swap(s2, s3);//跟上面效果一样

	if (s2.unique())std::cout << "资源是被唯一管理的" << std::endl; //用来判断资源是否是被唯一管理的

	s2.reset();//强制释放资源,将s2的use_count()置为0 指针指向nullptr
	s3.reset(new Person(10));//重置资源
	std::cout<< s3->a<< std::endl;

	return 0;
}

std::weak_ptr(弱引用指针)

weak_ptr 的作用是为了处理 shared_ptr 出现循环引用,导致出现引用计数不能为0,导致内存泄漏的问题

循环引用出现的示例代码

代码中Son 和Father 分别持有对方的智能指针。
son将要结束的时候,发现自己的成员中father的智能指针处于占用状态,所以son无法释放,father因为同样的情况也无法引用计数减到0 无法释放

#include <memory>
#include<iostream>
class Son;
class Father;
using fatherptr = std::shared_ptr<Father>;
using sonptr = std::shared_ptr<Son>;
class Father
{
public:
	sonptr s;
	Father();
	~Father();
};
class Son
{
public:
	fatherptr f;
	Son();
	~Son();
};

Father::Father() { std::cout << "hello father"<<std::endl; }
Father::~Father() { std::cout << "bye father"<<std::endl;}
Son::Son() { std::cout << "hellow son"<<std::endl; }
Son::~Son() { std::cout << "bye son" << std::endl; }

using fatherptr = std::shared_ptr<Father>;
using sonptr = std::shared_ptr<Son>;
int main() {

	fatherptr f(new Father());
	sonptr s(new Son());
    f->s = s;
    s->f = f;  //循环调用

	std::cout << f.use_count() << std::endl;//输出2
	std::cout << s.use_count() << std::endl;//输出2

    return 0;
}

weak_ptr解决循环引用

那么怎么解决,聪明的程序员就开发出了weak_ptr;

可以这么说,weak_ptr就是为了协助share_ptr而生。它本身不具备指针的功能没有重载的“*”和“->”.
weak_ptr相当与一个观察者,并不拥有 share_ptr的所有权;
它的成员函数都是为了监测shared_ptr所管理的资源而设计的。
weak_ptr 不会增加引用计数,因此可以打破 shared_ptr 的循环引用。
我们把其中一个成员指针改成弱引用指针就可以了。

#include <memory>
#include<iostream>
#include<algorithm>

class Son;
class Father;
using fatherptr = std::shared_ptr<Father>;
using sonweakptr = std::weak_ptr<Son>;
using sonptr = std::shared_ptr<Son>;
class Father
{
public:
	sonweakptr s;
	Father();
	~Father();
};
class Son
{
public:
	fatherptr f;
	Son();
	~Son();
};

Father::Father() { std::cout << "hello father"<<std::endl; }
Father::~Father() { std::cout << "bye father"<<std::endl;}
Son::Son() { std::cout << "hellow son"<<std::endl; }
Son::~Son() { std::cout << "bye son" << std::endl; }

int main() {

	fatherptr f(new Father());
	sonptr s(new Son());
    f->s = s;
    s->f = f;  //循环调用

	std::cout << f.use_count() << std::endl;
	std::cout << s.use_count() << std::endl;

    return 0;
}

参考资料
强烈推荐:视频讲解
智能指针的各种函数
C++ 智能指针的正确使用方式

https://changkun.de/modern-cpp/zh-cn/05-pointers/
https://blog.csdn.net/weixin_59253379/article/details/124840156

  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值