C++智能指针的理解使用

440 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

简介:

C++语言开发者在发展中不断优化其语法,增加更多的好用的语法糖,今天要给大家介绍的就是C++在11版本中增加的 智能指针

1、三种智能指针

  • std::shared_ptr;
    
  • std::unique_ptr;
    
  • std::weak_ptr;
    

备注: 在C++ 11 后 std::auto_ptr 已经被废弃了

2、智能指针的介绍

很多人都知道C/C++中的指针概念,一听到指针就很懵,指针里面的内容让人头皮发麻,不过放心,本文只讲比较专一的知识点,今天就重点看下智能指针的使用和特点。我们知道,C/C++自带的指针需要我们手动分配内存,手动释放,然后经常在开发的时候,很多开发人员会忘记释放申请的空间,从而导致了内存泄漏的问题,那么智能指针的出现就可以很好的避免这个问题,其实呀,智能指针就是对原始指针的封装,其会主动释放内存,不用担心内存泄漏,当然说到这里很多就有疑问,怎么自动释放内存呢,接下来我们一步步来分析。

但是补充一点,并不是所有的指针都可以封装成智能指正,而且在有些场合,有些业务条件下,原始指针比智能指针更加的方便

3、unique_ptr

  • 1、从名字上来看,都知道这个指针唯一,其实是理解为,在某个时刻,这个指针是唯一管理一份内容空间的
  • 2、与所有智能指针一样,当超出作用域的时候,指针指向的内存就会自动释放
  • 3、该指针不能复制,智能移动

3.1、unique_ptr的创建

  • 0、先看下原始指针
int main() {
	// 1 std::shared_ptr;
	// 2 std::unique_ptr;
	// std::unique_ptr<Person> pp = std::unique_ptr<Person>();
	// 3 std::weak_ptr;

	Person* p1 = new Person("张三");

	{
		Person* p2 = new Person("李四");
	}
	return 0;
} 

image.png 可以看到原始指针这种方式,并没有调用析构函数

int main() {
	// 1 std::shared_ptr;
	// 2 std::unique_ptr;
	// std::unique_ptr<Person> pp = std::unique_ptr<Person>();
	// 3 std::weak_ptr;

	Person* p1 = new Person("张三");

	{
		Person* p2 = new Person("李四");
		delete p2;
	}
	delete p1;
	return 0;
} 

image.png

  • 1、通过原始指针的方式来创建
	Person* p1 = new Person("张三");

	//通过原始指针来创建unique_ptr
	std::unique_ptr<Person> u_p1(p1);
	//可以看到两者的内容一直
	u_p1->printPerson();
	p1->printPerson();
	cout << "------------" << endl;
	u_p1->editPersonName("孙悟空");
	u_p1->printPerson();
	p1->printPerson();

image.png 可以看到,通过原始指针来创建的独占指针,在转换后,两者都可以访问内存数据

  • 2、通过new的形式来创建
	std::unique_ptr<Person> new_ptr{new Person("newPtr")};
	new_ptr->editPersonName("newPtrCreate");
	new_ptr->printPerson();

image.png 可以看到这种方式也是很好用的,这里的Person对象使我创建的一个自定义的类

  • 3、通过make_unique的形式来创建
	//通过make_unique的方式创建智能指针
	std::unique_ptr<Person> createPerson = std::make_unique<Person>();
	createPerson->editPersonName("createName");
	createPerson->printPerson();

image.png

3.2、unique_ptr的函数调用

1、通过传递值的方式

首先创建一个函数,打印这个智能指针下的内容

void though_by_value(std::unique_ptr<Person> p1) {
	p1->printPerson();
}

将智能指针传递过去


	std::unique_ptr<Person> createPerson = std::make_unique<Person>();
	createPerson->editPersonName("test1");
	createPerson->printPerson();

	though_by_value(createPerson);

image.png

可以看到这里是报错了的,很明显我们上面提到过智能指针智能独占内存,不能 copy 只能 move

因为我们使用下面这种方式

	std::unique_ptr<Person> createPerson = std::make_unique<Person>();
	createPerson->editPersonName("test1");
	createPerson->printPerson();

	though_by_value(std::move(createPerson));

image.png

但是在这里提示一点,当我们使用move的时候,createPerson就不能再调用他的函数了,因为,已经使用move传递过去了,可以理解 createPerson指针已经失去了对内存的占有

2、通过传递引用的方式 首先创建一个接受智能指针引用的方式

/*
	通过传递引用的方式
*/
void though_by_ref(std::unique_ptr<Person> &p2) {
	p2->printPerson();
	p2.reset();
}

在这里我们调用了 p2.reset(),当只能指针调用了reset的是,就不会再指向这个对象了,因此在下面的get地址函数打印当中我们可以看到,全部是0,当然在这里我们为了避免这种修改可以在函数的参数里面加上const,这样一来,就没办法调用reset

void though_by_ref(std::unique_ptr<Person> &p2) {
	p2->printPerson();
	p2.reset();
}
	std::unique_ptr<Person> createPerson = std::make_unique<Person>();
	createPerson->editPersonName("test2");
	createPerson->printPerson();
	though_by_ref(createPerson);
	cout << "地址" << createPerson.get() << endl;

image.png 当不修改指针的指向时下面的createPerson就能打印出来地址

image.png

3、链式调用

在本地函数中创建一个智能指针,并将智能指针返回

std::unique_ptr<Person> get_unique_ptr() {
	std::unique_ptr<Person> local_ptr = std::make_unique<Person>();
	local_ptr->editPersonName("localName");
	return local_ptr;
}

//链式调用
get_unique_ptr()->printPerson();

但是要注意,这种链式调用的地址在函数内的地址个get的地址是不一样的

std::unique_ptr<Person> get_unique_ptr() {
	std::unique_ptr<Person> local_ptr = std::make_unique<Person>();
	local_ptr->editPersonName("localName");
	cout << "loacl address " << &local_ptr << endl;
	cout << "loacl address get " << local_ptr.get() << endl;


	return local_ptr;
}

image.png

4、shared_ptr

共享指针

	//共享指针
	//1、常量指针
	std::shared_ptr<int> int_ptr  = make_shared<int>(200);
	cout << "value: " << *int_ptr << endl;
	cout << "use cout:" << int_ptr.use_count() << endl;

	std::shared_ptr<int> int_ptr2 = int_ptr;
	cout << "use int_ptr cout:" << int_ptr.use_count() << endl;
	cout << "copy after int_ptr2 use cout:" << int_ptr2.use_count() << endl;

	//一个指针改变,另外一个指针也会跟着改变
	*int_ptr2 = 30;
	cout << "value: " << *int_ptr << endl;

	//现在来看两个指针的引用次数
	std::shared_ptr<int> int_ptr3 = int_ptr;
	cout << "use int_ptr cout:" << int_ptr.use_count() << endl;
	cout << "usd int_ptr2 cout:" << int_ptr2.use_count() << endl;
	cout << "usd int_ptr3 cout:" << int_ptr3.use_count() << endl;

	int_ptr = nullptr;
	cout << "use int_ptr cout:" << int_ptr.use_count() << endl;
	cout << "usd int_ptr2 cout:" << int_ptr2.use_count() << endl;
	cout << "usd int_ptr3 cout:" << int_ptr3.use_count() << endl;

image.png

共享指针明显与独占指针有一个很大的不同就是,共享指针可以有很多个指针指向同一块,内存,并可以获取use_count,但是需要注意,共享指针的内存只有一块,因此,在释放的时候,也只会释放一次,并且是当所有的use_count都为0的时候,才会释放。

1、值传递

首先我们创建一个共享指针的函数

void though_by_value_share(std::shared_ptr<Person> p1) {
	p1->editPersonName("-hbhb");
	cout << p1->getName() << endl;
	cout << " use cout:" << p1.use_count() << endl;
}

在这个共享指针李我们修改名字,然后打印名字,再打印指针的引用次数

	std::shared_ptr<Person> pp = make_shared<Person>();
	pp->editPersonName("waibupp");
	though_by_value_share(pp);
	cout << "name:" << pp->getName() << endl;
	cout << "外部use cout" << pp.use_count() << endl;

在外部同样我们打印指针的名字,也打印指针的引用次数

image.png 可以看到,在函数内部修改的指针内存空间,在外部同样是修改了的,但是在函数内部我们发现,函数的指针次数是2,在函数外部是1,因为在函数执行完毕后,会释放一份指针。

2、引用传递

void though_by_ref_share(std::shared_ptr<Person> &p2) {
	p2->editPersonName("-hbhb");
	cout << p2->getName() << endl;
	cout << " use cout:" << p2.use_count() << endl;
}

通过引用的方式,将指针传递到函数体内

	//引用传递
	std::shared_ptr<Person> pp2 = make_shared<Person>();
	though_by_ref_share(pp2);
	cout <<"name:" << pp2->getName() << endl;
	cout << "use cout" << pp2.use_count() << endl;

同样我们在函数体内外打印一些数据

image.png 可以看到,use_cout 的值在函数传递前后并没有改变,且函数体内的值在改变后,同样,函数外面的指针指向的值也会发生改变

5、shared_ptr 和 unique_ptr

shared_ptr 是不能转换成 unique_ptr,但是unique_ptr可以转换成shared_ptr

	// std::unique_ptr<Person> pp3(std::move(pp2)); 不能这样转换
	std::unique_ptr<Person> pp3 = make_unique<Person>();
	std::shared_ptr<Person> pp4(std::move(pp3));

5、weak_ptr

weak_ptr是一种虚引用,其实它并不会对内存拥有占用的权限,因此,他无法使用-> 和*,weak_ptr的出现是为了解决循环依赖的问题,比如在一个类中拥有另一个类的引用,那么在释放的时候,就会出现无法直接释放的问题,这是一个经典的问题。因为我们通过weak_ptr来标记这个对象,在使用的时候,将其转换为其他指针,而在释放时,由于其并不会对内存占有引用,因此可以安全的释放,不必担心内存泄漏的风险。

1、use_cout 的引用问题

	std::shared_ptr<Person> ps = std::make_shared<Person>();
	std::weak_ptr<Person> weak_c(ps);
	cout << "use cout" << ps.use_count() << endl;
	cout << "weak use cout" << weak_c.use_count() << endl;

image.png 可以看到在使用weak_ptr后,use_cout并没有改变,因此可以说明weak_ptr只是一个标志,其并不会对内存进行占用

2、转换问题

	std::shared_ptr<Person> ps = std::make_shared<Person>();
	std::weak_ptr<Person> weak_c(ps);
	cout << "use cout" << ps.use_count() << endl;
	cout << "weak use cout" << weak_c.use_count() << endl;

	std::shared_ptr<Person> ps2 = weak_c.lock();
	cout << "shared_ptr use cout" << ps2.use_count() << endl;

image.png 从上面的例子来看,weak_ptr可以通过lock函数,将weak_ptr转换为shared_ptr,因此,转换后又的use_cout就会增加1 3、经典的循环引用问题 这个问题在开发过程中经常会出现,也就是在两个类中存在相互依赖的问题而导致不能释放的问题,首先我们设计一个Person的类

class Person {
public:
	Person() {}
	Person(string namestr) {
		this->name = namestr;
		cout << "Person的构造函数" << this->name <<endl;
	}
	~Person() {
		cout << "Person的析构函数" << this->name<<endl;
	}
	void printPerson() {
		cout <<"人员的名字为:" << this->name << endl;
	}
	string getName() {
		return this->name;
	}
	void editPersonName(string newName) {
		this->name = newName;
	}
	void setFriend(std::shared_ptr<Person> fri) {
		this->mFriend = fri;
	}
public:
	string name = std::string("biaoge");
	std::shared_ptr<Person> mFriend;

};

第一步我们首先创建简单的两个share_ptr

	std::shared_ptr<Person> p1 = std::make_shared<Person>("p1");
	std::shared_ptr<Person> p2 = std::make_shared<Person>("p2");

image.png 我们可以看到,直接创建后在执行完就会调用析构函数释放

第二步我们相互设置依赖

	std::shared_ptr<Person> p1 = std::make_shared<Person>("p1");
	std::shared_ptr<Person> p2 = std::make_shared<Person>("p2");
	p1->setFriend(p2);
	p2->setFriend(p1);

image.png

可以很明显的看到,没有调用析构函数,因为在释放的时候,相互依赖,计算机内存在不着急的情况下,如果有引用就不会释放。

第三步 使用weak_ptr

public:
	string name = std::string("biaoge");
	std::weak_ptr<Person> mFriend;

image.png 通过将Person的成员变量的类型改为weak_ptr,就会发现可以正常调用析构函数释放内存

6、全部代码

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <fstream>
#include <ostream>
#include <string>
#include <vector>
#include <algorithm>
#include <deque>
#include <list>
#include <queue>
#include <set>
#include <ctime>
#include <map>
#include <functional>

using namespace std;
//抽象人员
class Student {
public:
	string name;

};
void printVector(vector<Student> vec) {
	for (vector<Student>::iterator it = vec.begin(); it != vec.end(); it++) {
		cout << (*it).name << endl;
	}
}

//创建人员
void CreateStudent(queue<Student>& que, int num) {
	string setName = "ABCDEFGHIJKLMNOPK";
	int sum = rand() % 10;
	for (int i = 0; i < sum; i++) {
		Student stu;
		char buf[64] = { 0 };
		sprintf(buf,"%d",num);
		string s(buf);
		stu.name = "第";
		stu.name += s;
		stu.name += "层";
		stu.name += setName[i];
		que.push(stu);
	}
}
//mylist是电梯 que是队列容器,也就是每一层的人数 pushV是拷贝进电梯的人员
int PushList(list<Student> &mylist,queue<Student> &que,vector<Student> &pushV) {
	int tmppush = 0;//临时变量,记录出电梯的人员数
	while (!que.empty()) {
		if (mylist.size() > 15) {
			break;
		}
		//从每一层容器中取出一个人
		Student s = que.front();
		//拷贝到层数统计的vector当中去
		pushV.push_back(s);
		//进电梯
		mylist.push_back(s);
		//删除队列中的当前数据
		que.pop( );
		tmppush++;
	}
	return tmppush;
}
//mylist 电梯 que出电梯的人员  num 层数
int PopList(list<Student>& mylist, vector<Student> &popV, int num) {
	
	int temppop = 0;
	if (num == 17) { //当到达17层,所有的人都要出电梯
		while (!mylist.empty()) {
			Student s = mylist.front();
			//把出电梯的人拷贝到popV中
			popV.push_back(s);
			//删除电梯里的人
			mylist.pop_front();
			temppop++;
		}
	}
	int n = rand() % 5;//随机出电梯的人数
	if (n == 0) {
		return temppop;
	}
	//当电梯有人,且随机人数大于等于电梯的人数,
	if (mylist.size() > 0 && mylist.size() >= n) {
		for (int i = 0; i < n; i++) {
			Student stu = mylist.front();
			popV.push_back(stu);
			//删除电梯里的人
			mylist.pop_front();
			temppop++;

		}
	}
	return temppop;
}

void test05() {
	srand((unsigned int)time(NULL));

	list<Student> mylist;//创建电梯容器

	int Pushnum = 0;//进电梯的总人数
	int Popnum = 0; // 出电梯的总人数

	vector<Student> pushV; //记录进电梯的人员
	vector<Student> popV;  //记录出电梯的人员

	//电梯上升
	for (int i = 1; i < 18; i++) {
		//创建人员
		queue<Student> que;
		//创建的人员在哪一层
		CreateStudent(que, i);
		//判断是否能进电梯
		if (mylist.size() <= 15) {
			//17层不用进电梯
			if (i < 17) {
				//进电梯
				Pushnum += PushList(mylist, que, pushV);
			}
		}
		//判断出电梯的条件
		if (mylist.size() > 0) { //电梯有人才能出
			if (i > 1) { //第一层,电梯是空的
				//出电梯
				Popnum += PopList(mylist, popV, i);
			
			}
		
		}
	
	}
	//打印进电梯的人员
	printVector(pushV);
	cout << "进电梯的人数" << Pushnum << endl;
	//打印出电梯的人员
	printVector(popV);
	cout << "出电梯的人数" << Popnum << endl;
}

void test06() {
	multiset<int> s;
	s.insert(5);
	s.insert(2);
	s.insert(3);
	s.insert(4);
	s.insert(5);
	s.insert(5);
	s.insert(9);

	//set<int>::iterator it = s.find(19);
	//if (it == s.end()) {
	//	cout << "查找失败" << endl;
	//}
	//else {
	//	cout << "查找成功" << endl;
	//}
	cout << s.count(5) << endl;
}
#define SALE_DEPATMENT 1//销售部门
#define DEVELOP_DEPATMENT 2//研发部门
#define FINACIAL_DEPATMENT 3///财务部门


class Worker {

public:
	string name;
	int age;
	int Salary;

};

void CreateWorker(vector<Worker> &work) {
	
	srand((unsigned int)time(NULL));
	string setName = "ABCDE";
	for (int i = 0; i <5 ; i++) {

		Worker worker;
		worker.name = "员工";
		worker.name += setName[i];
		worker.age = rand() % 30 + 30;
		worker.Salary = rand() % 20000 + 20000;
		work.push_back(worker);

	}

}
void WorkerByGroup(vector<Worker> &work, multimap<int, Worker> &mwork) {
	//遍历员工
	for (vector<Worker>::iterator it = work.begin(); it != work.end(); ++it) {
		int id = rand() % 3 + 1;
		//员工保存到mwork中
		//mwork[]
		mwork.insert(make_pair(id, *it));
	}

}
void PrintWorker(multimap<int, Worker> &works) {


}
void test111() {
	//保存未分组的员工信息
	vector<Worker> vWorker;
	//保存分组后员工的信息
	multimap<int, Worker> mWokers;
	//创建员工信息
	CreateWorker(vWorker);
	//把分好组的员工放入mWokers
	WorkerByGroup(vWorker,mWokers);
	//打印员工信息
	PrintWorker(mWokers);


}

//第一步 继承binary_function<参数1,参数2,返回类型>
struct Myfunc:public binary_function<int,int,void>{

	void operator()(int val,int vals) const{ //第二步: 加上const成为常函数,常函数其实就是为了防止对成员变量的修改
		cout << val+ vals << endl;//第三步重写,函数体
	}
};
void  test007() {
	vector<int> v;
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);
	v.push_back(50);
	v.push_back(60);
	
}
class Person {
public:
	Person() {}
	Person(string namestr) {
		this->name = namestr;
		cout << "Person的构造函数" << this->name <<endl;
	}
	~Person() {
		cout << "Person的析构函数" << this->name<<endl;
	}
	void printPerson() {
		cout <<"人员的名字为:" << this->name << endl;
	}
	string getName() {
		return this->name;
	}
	void editPersonName(string newName) {
		this->name = newName;
	}
	void setFriend(std::shared_ptr<Person> fri) {
		this->mFriend = fri;
	}
public:
	string name = std::string("biaoge");
	std::weak_ptr<Person> mFriend;

};

void though_by_value(std::unique_ptr<Person> p1) {
	p1->printPerson();
}
/*
	通过传递引用的方式
*/
void though_by_ref(const std::unique_ptr<Person> &p2) {
	p2->printPerson();
}
std::unique_ptr<Person> get_unique_ptr() {
	std::unique_ptr<Person> local_ptr = std::make_unique<Person>();
	local_ptr->editPersonName("localName");
	cout << "loacl address " << &local_ptr << endl;
	cout << "loacl address get " << local_ptr.get() << endl;


	return local_ptr;
}

void though_by_value_share(std::shared_ptr<Person> p1) {
	p1->editPersonName("-hbhb");
	cout << p1->getName() << endl;
	cout << " use cout:" << p1.use_count() << endl;
}

void though_by_ref_share(std::shared_ptr<Person> &p2) {
	p2->editPersonName("-hbhb");
	cout << p2->getName() << endl;
	cout << " use cout:" << p2.use_count() << endl;
}
int main() {
	// 1 std::shared_ptr;
	// 2 std::unique_ptr;
	// std::unique_ptr<Person> pp = std::unique_ptr<Person>();
	// 3 std::weak_ptr;

	//Person* p1 = new Person("张三");
	//通过原始指针来创建unique_ptr
	//std::unique_ptr<Person> u_p1(p1);
	////可以看到两者的内容一直
	//u_p1->printPerson();
	//p1->printPerson();

	//cout << "------------" << endl;
	//u_p1->editPersonName("孙悟空");
	//u_p1->printPerson();
	//p1->printPerson();

	//通过new的方式来创建智能指针
	/*std::unique_ptr<Person> new_ptr{new Person("newPtr")};
	new_ptr->editPersonName("newPtrCreate");
	new_ptr->printPerson();*/

	//通过make_unique的方式创建智能指针
	//std::unique_ptr<Person> createPerson = std::make_unique<Person>();
	//createPerson->editPersonName("test1");
	//createPerson->printPerson();


	//std::unique_ptr<Person> createPerson = std::make_unique<Person>();
	//createPerson->editPersonName("test1");
	//createPerson->printPerson();
	//though_by_value(std::move(createPerson));

	//std::unique_ptr<Person> createPerson = std::make_unique<Person>();
	//createPerson->editPersonName("test2");
	//createPerson->printPerson();
	//though_by_ref(createPerson);
	//cout << "地址" << createPerson.get() << endl;

	//链式调用
	//get_unique_ptr()->printPerson();


	//共享指针
	//1、常量指针
	//std::shared_ptr<int> int_ptr  = make_shared<int>(200);
	//cout << "value: " << *int_ptr << endl;
	//cout << "use cout:" << int_ptr.use_count() << endl;

	//std::shared_ptr<int> int_ptr2 = int_ptr;
	//cout << "use int_ptr cout:" << int_ptr.use_count() << endl;
	//cout << "copy after int_ptr2 use cout:" << int_ptr2.use_count() << endl;

	////一个指针改变,另外一个指针也会跟着改变
	//*int_ptr2 = 30;
	//cout << "value: " << *int_ptr << endl;

	////现在来看两个指针的引用次数
	//std::shared_ptr<int> int_ptr3 = int_ptr;
	//cout << "use int_ptr cout:" << int_ptr.use_count() << endl;
	//cout << "usd int_ptr2 cout:" << int_ptr2.use_count() << endl;
	//cout << "usd int_ptr3 cout:" << int_ptr3.use_count() << endl;

	//int_ptr = nullptr;
	//cout << "use int_ptr cout:" << int_ptr.use_count() << endl;
	//cout << "usd int_ptr2 cout:" << int_ptr2.use_count() << endl;
	//cout << "usd int_ptr3 cout:" << int_ptr3.use_count() << endl;

	//值传递
	//std::shared_ptr<Person> pp = make_shared<Person>();
	//pp->editPersonName("waibupp");
	//though_by_value_share(pp);
	//cout << "name:" << pp->getName() << endl;
	//cout << "外部use cout" << pp.use_count() << endl;


	//引用传递
	//std::shared_ptr<Person> pp2 = make_shared<Person>();
	//though_by_ref_share(pp2);
	//cout <<"name:" << pp2->getName() << endl;
	//cout << "use cout" << pp2.use_count() << endl;
	//
	//// std::unique_ptr<Person> pp3(std::move(pp2)); 不能这样转换
	//std::unique_ptr<Person> pp3 = make_unique<Person>();
	//std::shared_ptr<Person> pp4(std::move(pp3));

	//std::move(pp2);


	//std::shared_ptr<Person> ps = std::make_shared<Person>();
	//std::weak_ptr<Person> weak_c(ps);
	//cout << "use cout" << ps.use_count() << endl;
	//cout << "weak use cout" << weak_c.use_count() << endl;

	//std::shared_ptr<Person> ps2 = weak_c.lock();
	//cout << "shared_ptr use cout" << ps2.use_count() << endl;

	std::shared_ptr<Person> p1 = std::make_shared<Person>("p1");
	std::shared_ptr<Person> p2 = std::make_shared<Person>("p2");
	p1->setFriend(p2);
	p2->setFriend(p1);


	return 0;
}