c++lab-智能指针与引用计数的垃圾回收机制

背景

上世纪九十年代末期,随着java和c#的兴起,垃圾自动回收机制开始成为一种潮流,这个时候,也就是c++98定义了一种新标准:auto_ptr,提供了垃圾自动回收的机制。

1.auto_ptr

使用方式比较简单

//需要引用std的命名空间
class Player {
public:
	int id;
	string name;
	Player(int id,string name) {
		this->id = id;
		this->name = name;
	}
};
int main()
{
	auto_ptr<Player> ptr(new Player(2, "mms"));
	cout << ptr->id << endl;
}

不过这玩意过于古老,c++11已经抛弃了它,拥抱unique_ptr和shared_ptr

2. unique_ptr

测试代码:当指针被回收时顺带的带走堆里面的数据
void test() {
	unique_ptr<Player> pp(new Player(2, "mms"));
	cout << pp->name << endl;
}
int main()
{
	test();

}
测试结果

在这里插入图片描述

测试代码:当指针置空的时候带走堆里面的数据
void test2() {
	unique_ptr<Player> pp(new Player(2, "mms"));
	cout << pp->name << endl;
	pp = nullptr;
}
int main()
{
	test2();

}

在这里插入图片描述

测试结果

在这里插入图片描述

测试代码:当指针指向其他堆对象时[见异思迁],带走此时的对象
void test3() {
	unique_ptr<Player> pp(new Player(2, "mms"));
	cout << pp->name << endl;
	pp = make_unique<Player>(3, "mme");
}

在这里插入图片描述
在这里插入图片描述
测试代码:当该堆对象有人接盘时,它的前任去接盘其他堆对象时,无法带走这人
这个测试案例比较复杂
设:
ptr a = T1Heap
ptr b = a
ptr a = T2Heap
此时,a不会顺带的干掉T1Heap

void test3() {
	unique_ptr<Player> pp(new Player(2, "mms"));
	cout << pp->name << endl;
	unique_ptr<Player> pp2;
	pp2 = move(pp);
	pp = make_unique<Player>(3, "mme");


}

在这里插入图片描述
在这里插入图片描述
这个时候其实完全可以把unique_ptr当成java或者c#里面的引用来用,只是有些区别就是,堆对象唯一指向一个unique_ptr,当你希望其他unique_ptr去持有这个堆对象时,你只能通过move,也就是将其他指针的对象抢过来,其它指针就没了,典型的一夫一妻制度。
我们可以手动让一个程序崩溃掉
在这里插入图片描述

3.shared_ptr

前面讲了unique_ptr,以一夫一妻去约束你的代码,其实对程序员来讲是不自由的,比如对于游戏来讲,我们设计了这样一个框架,存在装备系统,背包系统,技能系统,为了解耦,然我我们每个系统都会定义一个单独的Player指针用于访问某个player,如果我们使用unique_ptr,是不是同一时间只有一个系统去持有,如果我们用单例模式将这个player封装起来就当我没说,因为这个时候代码耦合性会变大,因为我们在写文本代码的时候都会使用xxx.instance.do
这样的句式,当我们尝试剥离装备系统给其他游戏使用时 ,这个时候我们就必须要定义一个Player的Instance,这样会导致你的代码复用性变得很差,为了解决这个问题,我们会使用一种叫代理的东西,

在这里插入图片描述

相当于对LocalPlayer进行了一层隔离,这个时候,如果我们单独剥离背包系统给项目组B使用,他们不需要去管LocalPlayer的逻辑(他们有自己的LocalPlayer逻辑),但是背包系统,技能系统,装备系统的代码却不需要去修改,或者只是做微调,这是一种很棒的设计,当然实现的时候unique_ptr其实就不太适合

#pragma once
class Lab1
{
public:
	Lab1();
	~Lab1();
};

class LocalAgent {


public:
	LocalAgent(void* player) {
		
	}
	virtual void onEquip() {}
	virtual void onBag() {}
	virtual void onSkill() {}
};
class EquipSys {
public:
	LocalAgent* agent;

};

class BagSys {
public:
	LocalAgent* agent;

};

class SkillSys {
public:
	LocalAgent* agent;

};

用指针当然非常方便,但是如果你忘记回收,就会…boom!内存泄露,用unique就会产生限制,毕竟是一夫一妻制,这相当于给Agent手动**
我们可以使用shared_ptr

void test4() {
	shared_ptr<Player> pp;
	pp = make_shared<Player>(123, "mms");
	cout << pp->name << endl;
	shared_ptr<Player> p2p;
	p2p = pp;//让p2p指向mms
	cout << p2p->name << " " << pp->name << endl;
	pp = make_shared<Player>(456, "mme");

}

在这里插入图片描述
显然是支持我们的这种用法的

4. unque_ptr的实现机制

其实讨论它的实现机制没有太多意义,要达成类似的效果其实很简单,前面我们对unique_ptr的各种情况作了测试,可以发现,它和栈对象的机制非常像,只要离开了自己的[作用域],就会析构掉,然后顺便带走堆里面的数据,所以,unique_ptr本质上就是用一个栈对象去包裹这个堆对象,用法跟栈对象其实差不多,但为要用呢?我们讨论一个基本的情况
假如你使用

vector<Player> list

你能否将函数栈里面的Player pp注入到全局list?看似你已经完成了list.push_back(pp)的操作,其实已经狸猫换太子了,这个时候的list[0]已经不是你创建出来的那个pp,而是pp的copy…
有时候,我们不希望这个pp是个copy…
在这里插入图片描述
其实我一直觉得这个机制很奇葩,很鸡贼,因为之前一直用的是c#,java
现在,让我们找回c#那种感觉

在这里插入图片描述
现在感觉是不是回来啦~~~
一次定义,不需要拷贝,不用狸猫换太子,哎,这种感觉太棒了

所以unique_ptr的机制就是借助栈对象回收的那个瞬间,把它的灵魂伴侣一同拉到地狱,如果不想一起陪葬,就move,跟着另外一个unique_ptr跑就对了…

5.shared_ptr的机制

前面我们对shared_ptr有了较为深刻的理解,其实shared_ptr已经和c#或者java的引用类型很像了,支持多个引用变量访问同一个堆对象,这个时候我们要将这个堆对象放到vector就非常之简单了
在这里插入图片描述
回过头来,我们可以思考shared_ptr的机制,运行多个指针去访问堆,当这些指针都置空时,这个堆自动释放,显然,这是种依赖于计数的垃圾回收机制,也就是引用计数法

6. 引用计数法

引用计数法理解起来其实是非常简单的,其实我们一般考虑两种情况就可以了

6.1 计数法的递归回收
class Player {
public:
	int id;
	string name;
	Player(int id,string name) {
		this->id = id;
		this->name = name;
	}
	~Player() {
		cout << "im release "<<name<<endl;
	}
};

比如上述这种代码,它不持有子引用,当我们直接回收
在这里插入图片描述
但是我们如果持有子引用:

class TB {

public:

	int id;
	~TB() {
	
		cout << "tb release " << endl;
	}
};
class Player {
public:
	int id;
	string name;
	TB* tb;
	Player(int id,string name) {
		this->id = id;
		this->name = name;
	}
	~Player() {
		cout << "im release "<<name<<endl;
	}
};
int main()
{
	shared_ptr<Player> pp = make_shared<Player>(123, "mms");
	pp->tb = new TB();
	pp->tb->id = 444;
	pp = nullptr;
	

	
}

在这里插入图片描述
此时的tb没有被回收

其实我想表达的是什么呢?两个问题,
第一个:计数回收应该学会递归的将tb回收掉
第二个:当我们定义了一个非shared_ptr的TB* tb变量时,是不会触发计数回收的递归特性

修改代码之后

class Player {
public:
	int id;
	string name;
	shared_ptr<TB> tb;
	Player(int id,string name) {
		this->id = id;
		this->name = name;
	}
	~Player() {
		cout << "im release "<<name<<endl;
	}
};

再来看
在这里插入图片描述
tb被回收掉了
显然触发了递归回收

6.2 循环引用

一个邪恶的例子

class TB {

public:

	int id;
	TB(int id){this->id = id;}
	~TB() {
	
		cout << "tb release " << endl;
	}
	shared_ptr<Player> player;
};
class Player {
public:
	int id;
	string name;
	shared_ptr<TB> tb;
	Player(int id,string name) {
		this->id = id;
		this->name = name;
	}
	~Player() {
		cout << "im release "<<name<<endl;
	}
};

当我们定义
在这里插入图片描述
循环的基本定义是让pp->tb = tb;tb->player = pp
就会构成一个环
在这里插入图片描述
此时就没有回收,怎么解决这个问题呢?使用weak_ptr

7. weak_ptr

class Player;

class TB {

public:

	int id;
	TB(int id) {
	
		this->id = id;
	}
	~TB() {
	
		cout << "tb release " << endl;
	}
	weak_ptr<Player> player;
};
class Player {
public:
	int id;
	string name;
	weak_ptr<TB> tb;
	Player(int id,string name) {
		this->id = id;
		this->name = name;
	}
	~Player() {
		cout << "im release "<<name<<endl;
	}
};

在这里插入图片描述
此时就能被回收了,but…
在这里插入图片描述
如果结束作用域的话,当pp触发析构时,会自动回收掉自己的weak_ptr
在这里插入图片描述
所以weak_ptr也是安全的

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值