第12章 动态内存

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


动态内存

动态分配的对象的生存期间与它们在哪里创建是无关的,只有当显示释放时,这些对象才会销毁。
静态内存用来保存局部的static对象、类static成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static对象。分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static对象在使用之前分配,在程序结束时销毁。
除了静态内存和栈内存,每个程序还拥有一个内存池。这部分被称作自由空间或者堆,程序用堆来储存动态分配的对象,即在那些程序运行时分配的对象。动态对象的生存期由程序来控制,我们的代码必须显示的管理他们。

一、动态内存与智能指针:

new、delete
动态内存的使用很容易出问题,因为在正确的时间释放内存是很及其困难的。忘记释放内存会产生内存泄漏,在尚有指针应用内存的情况下就释放了它,这种情况下会产生引用非法内存的指针。
shared_ptr允许多个指针指向同一个对象,unique_ptr则“独占”所指向的对象。同时还定义了一个名为weak_ptr的伴随类,他是一种弱引用,指向shared_ptr所管理的对象,这三种类型都定义在memory头文件中。

shared_ptr类

智能指针也是模板,需要提供额外的信息–指针可以指向的类型。

make_shared<T>(args)  返回一个shared_ptr
shared_ptr<T> p(q) p是q的拷贝,会增加q的计数器。 
p=q  p原来指向的指针引用计数减一,q的引用计数加一。
p.unique()若p.use_count()1,返回true,否则返回false
p.use_count()   返回与p共享对象的只能指针数量,但是可能很慢

make_shared函数

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。

shared_ptr<int> p3 = make_shared<int>(42);
shared_ptr<string> p4 = make_shared<string>(10,'9');
shared_ptr<int>p5 = make_shared<int>();

类似顺序容器的emplace成员,make_shared用其参数来构造给定类型的对象。我们不传递任何参数时,就会发生值初始化。

shared_ptr的拷贝和赋值

每个shared_ptr都有一个关联的计数器,通常称为引用计数。
一旦一个shared_ptr的计数器为0,他就会自动释放自己所管理的对象:
auto r=make_shared(42);
r=q;//r原来指向的对象没有引用者,被自动释放。

shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。

由于在最后一个shared_ptr销毁前内存都不会释放,保证shared_ptr在无用之后不再保留旧非常重要了。所以,将shared_ptr存放在一个容器中随后重拍了容器时,应该确保erase删除那些不在需要的shared_ptr元素。

使用了动态生存期的资源的类

使用动态内存的原因:
1.程序不知道自己需要使用多少对象:例如容器
2.程序不知道所需对象的准确类型:见15章
3.程序需要在多个对象间共享数据:如一些类

直接管理内存

C++定义了两个运算符来分配和释放内存。运算符new分配内存,delete负责释放new分配的内存。

使用new动态分配和初始化对象

在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针:

int* pi=new int;

默认情况下,动态分配的对象是默认初始化的(见第二章),这意味这内置类型或者组合类型的对象的值将是未定义的,而类类型对象将用默认构造函数进行初始化:

string *ps = new string;
int *pi = new int;

我们可以用使用直接初始化、构造函数(圆括号)、列表初始化的方式来初始化一个动态分配的对象。
也可以对动态内存分配的对象进行值初始化,只需在类型名之后跟一对空括号即可:

string *ps1 = new string;  //默认初始化为空string
string *ps = new string(); //值初始化为空string
int *pi1 = new int; //*pi1的值未定义
int *pi2 = new int();

值初始化的内置类型对象有着良好定义的值。而默认初始化的对象的值则是未定义的。
我们可以使用一个括号包围的初始化器,它可以使用auto推断想要分配的对象的类型。

auto p1 = new auto(obj);
auto p2 = new auto{a,b,c};   //括号中只能有单个的初始化器。

尽量不要使用相同的原始指针来创建多个shared_ptr对象,因为在这种情况下,不同的shared_ptr对象不会知道它们与其他shared_ptr对象共享指针。

这样会造成什么样的问题?
设想两个shared指针由相同的原始指针创建:

int *rawPtr = new int();
std::shared_ptr ptr_1(rawPtr);
std::shared_ptr ptr_2(rawPtr);

假设ptr_2超出范围,那么它将删除关联的原始指针,这样ptr1就会指向一个悬挂指针。

所以,当ptr_1超出范围,那么它将再次尝试删除相关的内存,这实际上是一个悬挂的指针。因此程序会崩溃。

动态分配的const对象

用new分配const是合法的,返回的是一个指向const的指针。

内存耗尽

通过delete表达式释放内存

指针和delete

动态对象的生存期直到被释放为止

可以delete之后重置指针值为nullptr,但是只提供了有限的保护

也不要使用get初始化另一个智能指针或是为智能指针赋值

智能指针定义了一个名为get的函数,他返回一个内置指针,指向智能指针管理的对象。所以用此函数初始化一个shared_ptr会导致两个指向同一块内存的指针引用计数单独计数,容易出现问题。

其他shared_ptr操作

我们可以使用reset来讲一个新的指针赋予一个shared_ptr:

p=new int(1024); //错误,不能将一个指针赋给shared_ptr
p.reset(new int(1024)); //正确:p指向一个新对象

reset一样会更新引用计数,如果需要的话,会释放p指向的对象。reset成员经常与unique一起使用,来控制多个shared_ptr共享的对象。

if(!p.unique())
	p.reset(new string(*p));
	*p+=newVal;

智能指针和异常

如果使用智能指针,即使程序块过早结束,智能指针类也能确保在内存不在需要时将其释放:

如果使用智能指针管理的资源不是new分配的内存,记住传递给他一个删除器

unique_ptr

一个unique_ptr“拥有”它所指向的对象。与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定的对象。荡unique_ptr被销毁时,它所指向的对象也被销毁。
我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。类似shared_ptr,初始化unique_ptr必须采用直接初始化的形式:

unique_ptr<double> p2(new int(42));

由于一个unique拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作:

unique_ptr<string> p2(p1); //错误:unique_ptr不支持拷贝
unique_ptr<string>p3;
p3=p2      //错误:unique_ptr不支持赋值

虽然我们不能拷贝或者赋值unique_ptr,但可以通过调用release或者reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique:

unique_ptr<string> p2(p1.release());
unique_ptr<string>p3(new string("Trex"));
p2.reset(p3.release());

调用release会切断unique_ptr和它原来管理的对象之间的联系。release返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值。在本例中,管理内存的责任简单地从一个智能指针转移给另一个。但是,如果我们不用另一个智能指针来保存release返回的指针,我们的程序就要负责资源的释放。

p2.release();   //错误,p2不会释放内存,而且我们丢失了指针
auto p=p2.release(); //正确,但我们必须记得delete(p)。

传递unique_ptr参数和返回unique_ptr

不能拷贝unique_ptr有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr。最常见的例子是从一个函数返回一个unique_ptr。

向unique_ptr传递删除器

unique_ptr<objT,delT>p(new objT,fcn);

weak_ptr

weak_ptr是一种不控制所指对象生存期的智能指针,他指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放,因此,weak_ptr的名字攥住了这种智能指针“弱”共享对象的特点。
1、我们创建一个weak_ptr时,要用一个shared_ptr来初始化它:

auto p=make_shared<int>(42);
weak_ptr<int> wp(p);

本例中wp和p指向相同的对象。由于是弱共享,weak_ptr所指向的对象可能被释放掉。
由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock函数。此函数用于检查weak_ptr所指向的对象是否存在。如果存在,lock返回一个指向共享对象的shared_ptr,如果不存在则返回一个空shared_ptr。

动态数组

new和数组

allocator类

当分配一大块内存是,我们通常计划在这块内存上按需求构造对象。在此情况下,我们希望将内存分配和对象构造分离。这意味着我们可以分配大块内存,但只在真正需要时才真正执行对象创建操作。

allocate类

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值