C++ 智能指针

首先应该就是提问为什么要使用智能指针?
简单来说:普通指针申请内存的时候,在使用完一定要记得释放内存,否则就好出现内存泄漏的问题,尤其在嵌入式设备内存很小的时候,编码的时候尤为要注重该问题。智能指针就是帮我们C++程序员管理动态分配的内存的,它会帮助我们自动释放new出来的内存,从而避免内存泄漏!
简单的例子:

// 动态分配内存,虽然有释放内存的代码,但是在半路return的时候没有做delete操作
int memoryLeak() {
	string *str = new string("内存泄露!");
	// ...此处省略功能代码
	// 发生某些异常,需要结束函数
	if (1) {
		return -1;
	}
	/
	// 另外,使用try、catch结束函数,也会造成内存泄漏!
	/
	delete str;	// 虽然写了释放内存的代码,但是遭到函数中段返回,使得指针没有得到释放
	return 1;
}

int main(void) {

	memoryLeak();
	
  // ...此处省略功能代码
	return 0;
}

memoryLeak函数虽然有释放内存的代码,但是在半路return的时候没有做delete操作,这样的这块内存则一直str 指针指向的地址上的内存空间被一直占用,直到主函数main在执行结束后return,这块内存才会被系统回收掉。

思考:如果我们分配的动态内存都交由有生命周期的对象来处理,那么在对象过期时,让它的析构函数删除指向的内存,这样是否可以实现内存自动管理呢
其实智能指针就是通过这个原理来解决指针自动释放的问题!

C++ 智能指针
一、auto_ptr
二、unique_ptr
三、shared_ptr
四、weak_ptr
五、智能指针的使用陷阱

一、auto_ptr
目前工程代码里一般不在使用auto_ptr了,基本都被unique_ptr替代了,所以想要深入了解这块的知识,可以自行去深入了解,这里就不介绍了。
auto_ptr 被C++11抛弃的主要原因:
1.复制或者赋值都会改变资源的所有权

// auto_ptr 被C++11抛弃的主要原因
auto_ptr<string> p1(new string("I'm Li Ming!"));
auto_ptr<string> p2(new string("I'm age 22."));
//打印P1、P2的地址
cout << "p1:" << p1 << endl;  // 0x01234A560
cout << "p2:" << p2 << endl;  // 0x01234A56a
//
p1 = p2;	
cout << "p1 = p2 赋值后:" << endl;
cout << "p1:" << p1 << endl;  //0x01234A56a
cout << "p2:" << p2 << endl;  // NULL
// p2赋值给p1后,首先p1会先将自己原先托管的指针释放掉,然后接收托管p2所托管的指针,
// 然后p2所托管的指针制NULL,也就是p1托管了p2托管的指针,而p2放弃了托管。

2.不支持对象数组的内存管理

auto_ptr<int[]> array(new int[5]);	// 不能这样定义

3.在STL容器中使用auto_ptr存在着重大风险,因为容器内的元素必须支持可复制和可赋值

vector<auto_ptr<string>> vec;
auto_ptr<string> p3(new string("I'm P3"));
auto_ptr<string> p4(new string("I'm P4"));

// 必须使用std::move修饰成右值,才可以进行插入容器中
vec.push_back(std::move(p3));
vec.push_back(std::move(p4));

cout << "vec.at(0):" <<  *vec.at(0) << endl;
cout << "vec[1]:" <<  *vec[1] << endl;


// 风险来了:
vec[0] = vec[1];	// 如果进行赋值,问题又回到了问题1,vec[1]成NULL
cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;

二、unique_ptr
5. 基于排他所有权模式:两个指针不能指向同一个资源
2.无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
3.保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
4.在容器中保存指针是安全的

unique_ptr<string> p1(new string("I'm Li Ming!"));
unique_ptr<string> p2(new string("I'm age 22."));
//打印P1、P2的地址
cout << "p1:" << p1 << endl;  // 0x01234A560
cout << "p2:" << p2 << endl;  // 0x01234A56a

//p1 = p2;					           // 禁止将左值P2赋值给P1
//unique_ptr<string> p3(p2);	// 禁止将左值P2赋值给P3

// p1赋值给p2后,首先p2会先将自己原先托管的指针释放掉,然后接收托管p1所托管的指针,
// 然后p1所托管的指针制NULL,也就是p2托管了p1托管的指针,而p1放弃了托管。
unique_ptr<string> p2(std::move(p1));  // 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样

cout << "p1 = p2 赋值后:" << endl;
cout << "p1:" << p1 << endl; //0x01234A56a
cout << "p2:" << p2 << endl; //NULL

三、shared_ptr
熟悉了unique_ptr 后,其实我们发现unique_ptr 这种排他型的内存管理并不能适应所有情况,有很大的局限!如果需要多个指针变量共享怎么办?

如果有一种方式,可以记录引用特定内存对象的智能指针数量,当复制或拷贝时,引用计数加1,当智能指针析构时,引用计数减1,如果计数为零,代表已经没有指针指向这块内存,那么我们就释放它!这就是 shared_ptr 采用的策略!
在这里插入图片描述
例:

class Person {
public:
	Person(int v) {
		this->no = v;
		cout << "构造函数 \t no = " << this->no << endl;
	}

	~Person() {
		cout << "析构函数 \t no = " << this->no << endl;
	}

private:
	int no;
};

// 仿函数,内存删除
class DestructPerson {
public:
	void operator() (Person *pt) {
		cout << "DestructPerson..." << endl;
		delete pt;
	}
};
引用计数的使用
调用use_count函数可以获得当前托管指针的引用计数
int main()
{
	shared_ptr<Person> sp1;
	shared_ptr<Person> sp2(new Person(2)); // 拷贝构造
	
	// 获取智能指针管控的共享指针的数量	use_count():引用计数
	cout << "sp1	use_count() = " << sp1.use_count() << endl;  // 0
	cout << "sp2	use_count() = " << sp2.use_count() << endl << endl;  //1
	
	// 共享
	sp1 = sp2; //赋值
	
	cout << "sp1	use_count() = " << sp1.use_count() << endl; //2
	cout << "sp2	use_count() = " << sp2.use_count() << endl << endl; //2
	
	shared_ptr<Person> sp3(sp1);
	cout << "sp1	use_count() = " << sp1.use_count() << endl; //3
	cout << "sp2	use_count() = " << sp2.use_count() << endl; //3
	cout << "sp2	use_count() = " << sp3.use_count() << endl << endl; //3
	
	return 0;
  //如上代码,shared_ptr<Person> sp2(new Person(2));  sp1 = sp2; 和 shared_ptr< Person > sp3(sp1);就是在使用引用计数了。
}

1.初始化

1). 方式一:构造函数
shared_ptr<int> up1(new int(10));  // int(10) 的引用计数为1
shared_ptr<int> up2(up1);  // 使用智能指针up1构造up2, 此时int(10) 引用计数为2

2). 方式二:使用make_shared 初始化对象,分配内存效率更高(推荐使用)
make_shared函数的主要功能是在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr; 用法:
make_shared<类型>(构造类型对象需要的参数列表);
shared_ptr<int> up3 = make_shared<int>(2); // 多个参数以逗号','隔开,最多接受十个
shared_ptr<string> up4 = make_shared<string>("字符串");
shared_ptr<Person> up5 = make_shared<Person>(9);

2.主动释放对象
shared_ptrr<int> up1(new int(10));
up1 = nullptr ;	// int(10) 的引用计数减1,计数归零内存释放 
// 或
up1 = NULL; // 作用同上 

3.重置
p.reset() ; 将p重置为空指针,所管理对象引用计数 减1
p.reset(p1); 将p重置为p1(的值),p 管控的对象计数减1,p接管对p1指针的管控
p.reset(p1,d); 将p重置为p1(的值),p 管控的对象计数减1并使用d作为删除器
p1是一个指针!

shared_ptr使用陷阱

shared_ptr作为被管控的对象的成员时,小心因循环引用造成无法释放资源!

如下代码:
Boy类中有Girl的智能指针;
Girl类中有Boy的智能指针;
当他们交叉互相持有对方的管理对象时…

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

class Girl;
class Boy {
public:
	Boy() {
		cout << "Boy 构造函数" << endl;
	}

	~Boy() {
		cout << "~Boy 析构函数" << endl;
	}

	void setGirlFriend(shared_ptr<Girl> _girlFriend) {
		this->girlFriend = _girlFriend;
	}

private:
	shared_ptr<Girl> girlFriend;
};

class Girl {
public:
	Girl() {
		cout << "Girl 构造函数" << endl;
	}

	~Girl() {
		cout << "~Girl 析构函数" << endl;
	}

	void setBoyFriend(shared_ptr<Boy> _boyFriend) {
		this->boyFriend = _boyFriend;
	}

private:
	shared_ptr<Boy> boyFriend;
};

void useTrap() {
	shared_ptr<Boy> spBoy(new Boy());
	shared_ptr<Girl> spGirl(new Girl());
	// 陷阱用法
	spBoy->setGirlFriend(spGirl);
	spGirl->setBoyFriend(spBoy);
	// 此时boy和girl的引用计数都是2
}

int main(void) {
	useTrap();
	system("pause");
	return 0;
}

四、weak_ptr
弱指针的作用就是上面提到的share_ptr指针带来的交叉使用智能指针, 带来内存泄露的风险
weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。 同时weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象。

弱指针的使用;
    weak_ptr wpGirl_1; // 定义空的弱指针
    weak_ptr wpGirl_2(spGirl); // 使用共享指针构造
    wpGirl_1 = spGirl; // 允许共享指针赋值给弱指针

    弱指针也可以获得引用计数;
    wpGirl_1.use_count()

    弱指针不支持 *-> 对指针的访问;

五、智能指针的使用陷阱
不要把一个原生指针给多个智能指针管理;

  int *x = new int(10);
    unique_ptr< int > up1(x);
    unique_ptr< int > up2(x);
    // 警告! 以上代码使up1 up2指向同一个内存,非常危险
    或以下形式:
    up1.reset(x);
    up2.reset(x);
    
	记得使用u.release()的返回值;
	在调用u.release()时是不会释放u所指的内存的,这时返回值就是对这块内存的唯一索引,如果没有使用这个返回值释放内存或是保存起来,这块内存就泄漏了.
	
	禁止delete 智能指针get 函数返回的指针;
	如果我们主动释放掉get 函数获得的指针,那么智能 指针内部的指针就变成野指针了,析构时造成重复释放,带来严重后果!
	
	禁止用任何类型智能指针get 函数返回的指针去初始化另外一个智能指针!
	shared_ptr< int > sp1(new int(10));
	// 一个典型的错误用法 shared_ptr< int > sp4(sp1.get());

以上文章内容参考https://blog.csdn.net/cpp_learner的c++ 智能指针内容,如不清楚,可去原文章深入

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值