c++三种智能指针shared_ptr、weak_ptr、unique_ptr的原理和使用

c++智能指针总结

一、智能指针出现的原因

1. 基于我们的的编程习惯,在堆区动态管理的资源忘记释放或者回收了,导致内存泄漏。
2. 有多个指针指向同一片内存的问题,造成内存资源的重复释放或回收。
3. 程序在在抛出异常前申请了资源,以至于异常抛出时导致程序中断,无法执行析构函数delete内存从而导致的内存泄漏。

基于上面三个主要的原因,聪明的程序员就提出了智能指针方便管理我们自己的内存,一定程度上解决了c++为了人所诟病的内存管理问题。
注意:三种指针包含在头文件< memory >.

二、智能指针的本质及其原理

  • 智能指针的本质一个对象,是一个行为表现都像指针的对象。它的封装利用了RAII机制或者思想,其RAII机制或者思想是“资源获取就是初始化”,是C++语言的一种管理资源、避免泄漏的惯用法。
  • 智能指针可分为两部分,一个是原指针,一个是引用计数(关联的计数器)。创建一个智能指针的时候引用计数为1,当引用计数为0的时候,智能指针本身会自动释放。当该智能指针被其他指针所用,引用计数就会相应的叠加,可以这么理解:引用计数的大小就是当前管理该内存的指针的数量。

三、三种智能指针的介绍

1.shared_ptr

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

#include 
#include
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;
}

2.重载原始指针的“*”和“->”

#include 
#include
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;
		return 0;
}

3.其他成员函数的作用和使用

#include 
#include
#include
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;
}

2.weak_ptr

关于弱指针,我们先要追究一下它的来由,看下面代码:

#include 
#include
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;
}

有如下结果:
在这里插入图片描述
发现它并没有调用智能指针指向的对象的析构函数!!为啥呢??
原因是:在智能指针调用析构函数的时候先将use_count- -当use_count等于0的时候,释放所指对象的内存资源,但是这里它发现use_count等于1,相当于给它传递一个信息,还有指针在用这一片资源,不能释放!从而导致了这里内存的泄露,这也是share_ptr在这种循环调用中的缺陷!!

那么怎么解决,聪明的程序员就开发出了weak_ptr,可以这么说,weak_ptr就是为了协助share_ptr而生。它本身不具备指针的功能没有重载的“*”和“->”.
它的成员函数都是为了监测shared_ptr所管理的资源而设计的。

#include 
#include
class Person
{
public:
	Person(int a) :a(a) {}
public:
	int a;
};
int main() {
	std::shared_ptr<Person>s(new Person(2));
	std::weak_ptr<Person>w = s;
	std::weak_ptr<Person>w2(s);
	std::weak_ptr<Person>w3(w2);   //weak_ptr从一个shared_ptr或者weak_ptr进行对象构造的初始化

	std::cout << s.use_count() << std::endl;//输出为1,说明weak_ptr构造它的时候引用计数不会增加
	                                        //weak_ptr没有参与资源管理,只是对资源的监控
	std::cout << w2.use_count() << std::endl;

	auto s2 = w.lock();//返回一个指向该资源的shared_ptr
	
	if(w.expired())//判读weak_ptr监控的shared_ptr是不是空资源 相当于use_cout==0,但是这种方式更快
	{std::cout<<"shared_ptr指向的是空区域"<<std::endl; }
	else
	{std::cout<<"shared_ptr管理的资源不是空" ;}
	
	return 0;
}

那等于循环调用 我们如何通过弱指针来解决呢?

#include 
#include
#include

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++三种智能指针shared_ptr、weak_ptr、unique_ptr的原理和使用_第1张图片

这样就解决了shared_ptr循环调用的问题。

3.unique_ptr

unique顾名思义,唯一的指向一个对象,该对象不能被共享(指针和资源一对一),unique_ptr向比于原始指针,使得在出现异常的情况下动态资源得以释放,unique_ptr的释放规则是:unique_ptr从指针开始,到离开作用域时,释放其指向的对象资源。
unique_ptr用法:

#include
#include
int main()
{
	std::unique_ptr<int> u(new int(10));//绑定申请的堆区资源
	std::unique_ptr<int>u2(std::move(u));//转移所有权到u2(移动语义) 
	
	//不允许同一份资源,被多个unique_Ptr管理
	//std::unique_ptru2(u);不能拷贝
	//std::unique_ptru3=u;不能赋值

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

}

其余操作和shared_ptr类似。

shared_ptr常见错误补充

如果我们想在类的内部调用自身的智能指针,我们肯定会想到用this指针初始化一个智能指针,如下面的代码:

#include 
#include
#include
class Son;
class Father;
using fatherptr = std::shared_ptr<Father>;
using faweakptr = std::weak_ptr<Father>;
using sonweakptr = std::weak_ptr<Son>;
using sonptr = std::shared_ptr<Son>;
void handptr(const fatherptr& f, const sonptr& s)//增加测试函数作为 作为类中成员函数调用自身智能指针的定义实现
{
	std::cout << "测试函数" << std::endl;
}
class Father
{
public:
	sonweakptr s;
	Father();
	~Father();
	void test();
};
class Son
{
public:
	fatherptr f;
	Son();
	~Son();
};

Father::Father() { std::cout << "hello father"<<std::endl; }
Father::~Father() { std::cout << "bye father"<<std::endl;}
void Father::test() { handptr(fatherptr(this), s.lock()); }//在类内部成员函数调用自身智能指针
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;  
	f->test();//调用测试

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

    return 0;
}

我们会有如下结果:
c++三种智能指针shared_ptr、weak_ptr、unique_ptr的原理和使用_第2张图片
发生了意料之中的错误,听一片内存被释放了两次。
这时候我们就要引入新知识std::enable_shared_from_this,这是一个模板类,用法代码如下:

#include 
#include
#include
class Son;
class Father;
using fatherptr = std::shared_ptr<Father>;
using faweakptr = std::weak_ptr<Father>;
using sonweakptr = std::weak_ptr<Son>;
using sonptr = std::shared_ptr<Son>;
void handptr(const fatherptr& f, const sonptr& s)//增加测试函数作为 作为类中成员函数调用自身智能指针的定义实现
{
	std::cout << "测试函数" << std::endl;
}
class Father:public std::enable_shared_from_this<Father>//继承该模板类,传入本身类型
{
public:
	sonweakptr s;
	Father();
	~Father();
	void test();
};
class Son:public std::enable_shared_from_this<Son>
{
public:
	fatherptr f;
	Son();
	~Son();
};

Father::Father() { std::cout << "hello father"<<std::endl; }
Father::~Father() { std::cout << "bye father"<<std::endl;}
void Father::test() { handptr(shared_from_this(), s.lock()); }//传入父类的继承函数成员函数,该成员函数返回一个this的智能指针
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;  
	f->test();//调用测试

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

    return 0;
}

总结

智能指针能给我们资源管理带来极大的便捷,但是凡是都有两面性,智能指针所带来的便捷,其实是由性能消耗换来的,在追求极致性能的时候,不要盲目使用智能指针。

写了一个下午,给菜鸡一个鼓励一下吧~~

你可能感兴趣的:(c++,visual,studio,c语言)