C++动态内存

1、C++中的三种内存

C++中有三种内存,第一种“静态内存”用来保存局部static对象,类static数据成员以及任何定义在函数体之外的变量,例如:

int count_calls()
{
	static int cnt=0;// 局部static变量 
	return ++cnt;
} 

class A
{
	static int a;// 类static成员 
}; 
int A::a=1;

int num=10;// 定义在函数体之外的变量 

第二种叫做“栈内存”,用来保存定义在函数内的非static变量,例如:

int return_zero()
{
	int zero=0;// 定义在函数内的非static变量
	return zero;
} 

而第三种“自由空间”或者叫“堆”,则用来保存动态分配的对象,例如new出来的东西。

前两种内存中存放的对象,其生命周期是由编译器控制的,而堆中对象的生命周期是由程序员来控制的——程序员通过new得到指向某一对象的指针后,可以通过delete销毁该对象,并释放与之关联的内存。

2、内存泄漏、悬挂指针

对动态内存的管理不合理就会容易造成“内存泄漏”,即该收回的内存没有收回的情况。我们知道,一个new出来的东西最后一般要delete,就是为了防止内存泄漏。一种常见的情况是:

	int* ptr=new int[100];
	delete ptr;// 此时只是释放了内存空间,并没有将指针置为空,由此产生悬挂指针

写代码的人虽然有管理动态内存的观念,但仍然留下了一个漏洞:ptr“悬空”了,这就导致对ptr的访问可能会带来不可预期的结果,而这个漏洞在编译时根本无法发现。一种解决办法是,在delete ptr后给它赋上nullptr,但是,如果现在的代码是:

	int* ptr1=new int[100];
	int* ptr2=ptr1;
	delete ptr1;
	ptr1=nullptr;

ptr2仍然是悬挂指针,仍然有隐患。在实际应用中,把每个指向同一片内存的指针都找出来往往是困难的,可见,管理好动态内存并杜绝可能的漏洞并非易事。

3、智能指针

C++为了解决动态内存不好管理的问题,引入了智能指针,智能指针并不是严格意义上的指针,而是一个模板类,其行为和我们熟悉的指针一样。智能指针主要有两种:shared_ptr和unique_ptr,它们定义在头文件<memory>中。

(1)shared_ptr

shared_ptr允许多个指针指向同一个对象,这也是叫它“shared”的原因。作为一个模板类,shared_ptr的使用方法和其他模板类一样,例如:

	shared_ptr<string> p1;// p1可以指向一个string 

shared_ptr可以和new混用,也就是说,编译器允许显示地将一个普通指针转换为shared_ptr(注意隐式转换是不允许的),但是智能指针和普通指针混用并不是个好主意,标准库提供了一个叫做make_shared的函数,它会返回某个类型的shared_ptr,make_shared之于shared_ptr就如同new之于普通指针一样:


为了方便,当然可以配合auto使用,例如上面的第一行可以写成:

	auto p3=make_shared<int>(42);

这样代码更加清晰、简洁。

每个shared_ptr都有一个引用计数,当每当有一个shared_ptr指向同一个对象,这个计数值就会加1,可以用成员函数use_count()查看这个计数。当给一个shared_ptr赋新值时或是一个shared_ptr被销毁时,这个计数值就会减1,若减为0,shared_ptr会调用析构函数销毁对象,同时释放关联的内存。而shared_ptr本身作为一个对象也会被析构掉,这就避免了前面说的悬挂指针的问题。下面代码展示了多个shared_ptr是如何共享对象的:

	auto p1=make_shared<string>("c++ is the best language");
	auto p2(p1);
	auto p3(p1); 
	cout<<p1.use_count()<<" "
		<<p2.use_count()<<" "
		<<p3.use_count()<<endl;// 打印3 3 3
	p3=make_shared<string>("no, php is the best"); 
	cout<<p1.use_count()<<" "
		<<p2.use_count()<<" "
		<<p3.use_count()<<endl;// 打印2 2 1

(2)unique_ptr

只允许自己独占对象:



4、使用动态内存的动机:

C++ Primer中给出了三个原因:第一是预先不知道要用多少对象,二是不知道所需对象的准确类型,三是需要在多个对象间共享数据。第一种原因不难理解,例如vector就解决了这种问题,第二种原因这里不讨论(C++ Primer中在第15章有讲解),下面是第三种原因的一个例子:

	vector<string> v1;
	{
		vector<string> v2={"a","aa","aaa"};
		v1=v2;
	}

用v2初始化v1后,v2里的数据不见了,加入我们希望:当两个对象共享底层数据时,若其中一个对象被销毁了,底层数据不被销毁,怎么办?答案是给这个类加上数据成员shared_ptr,书中有一个自定义的StrBlob类,在此不再赘述。

附:

下表是一些shared_ptr和unique_ptr的常用操作:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值