动态内存
Posted summer-8918
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态内存相关的知识,希望对你有一定的参考价值。
到现在为止,我们编写的程序中所使用的对象都有着严格定义的生存期。
全局对象在程序启动时分配,在程序结束时销毁;
对于局部自动对象,当我们进入其定义所在的程序块时被创建,在离开块时销毁;
局部static对象在第一次使用前分配,在程序结束时销毁。
除了自动和static对象外,C++还支持动态分配对象。动态分配的对象的生存期与它们在哪里创建无关,只有当显式地被释放时,这些对象才会销毁。
动态对象的正确解释呗证明是编程中及其容易出错的地方。为了更安全地使用对象,标准库定义了两个只能指针类型来管理动态分配的对象。当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它。
我们的程序到目前为止只使用过静态内存或栈内存。静态内存用来保存局部static变量,类static数据成员,以及定义在任何函数之外的变量。
栈内存用来保存定义在函数内的非static对象。分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static对象在使用之前分配,在程序结束时销毁。
除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作自由空间或堆。程序用堆来存储动态分配的对象——即,那些在程序运行时分配的对象。
动态对象的生存期由程序性来控制,也就是说,当动态对象不再使用时,我们的代码必须显式地销毁它们。
在C++中,动态内存的通过一对运算符来完成:new和delete
new:在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化
delete:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
动态内存的使用很容易出现问题,因为确保在正确的时间释放内存是极其困难的。
(1) 忘记释放内存,会产生内存泄露
(2) 尚有指针引用内存的情况下释放它,会产生引用非法内存的指针
新的标准库提供了两种智能指针类型来管理动态对象:
shared_ptr允许对个指针指向同一个对象;
unique_ptr则“独占”所指向的对象
weak_ptr是一种弱引用,指向shared_ptr所管理的对象
这三种类型都定义在memory头文件中
1 shared_ptr类
shared_ptr<string> p1 //指向string
默认初始化的智能指针中保存着一个空指针
shared_ptr和unique_ptr都支持的操作
shared_ptr<T> sp |
空智能指针,可以指向类型为T的对象 |
unique_ptr<T> up |
|
p |
将p用作一个条件判断,若p指向一个对象,则为true |
*p |
解引用p,获得它指向的对象 |
p->men |
等价于(*p).men |
p.get() |
返回p中保存的指针。要小心使用,若智能指针释放了其对象,返回的指针所指向的对象就消失了 |
swap(p,q) |
交换p和q中的指针 |
p.swap(q) |
shared_ptr独有的操作
make_shared<T> (args) |
返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象 |
shared_ptr<T> p(q) |
p是shared_ptr q的拷贝;此操作会递增q中的计数器。q中的指针必须能转换为T* |
p=q |
p和q都是share_ptr,所保存的指针必须能相互转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放掉 |
p.unique() |
若p.use_count()为1,返回true;否则返回false |
p.use_count() |
返回与p共享对象的智能指针数量;可能会很慢,主要用于调试 |
最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。
shared_ptr<int>p3 = make_shared<int>(42);
//指向一个值为42的int的shared_ptr
可以认为每个shared_ptr都有一个关联的计数器,通常称为引用计数器。无论何时考诶一个shared_ptr,计数器都会递增。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁,计数器就能递减。
一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
shared_ptr自动销毁所管理的对象——通过构析函数完成销毁工作。
如果你将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素。
使用了动态生存期的资源的类
程序使用动态内存出于以下三种原因之一:
1 程序不知道自己需要使用多少对象
2 程序不知道所需对象的准确类型
3 程序需要在多个对象间共享数据
容器类是出于第一种原因使用动态内存的典型例子。
使用动态内存的一个常见原因是允许多个对象共享相同的状态。
2 直接内存管理
使用new动态分配和初始化对象
int *pi = new int;
//pi指向一个动态分配的、未初始化的无名对象
在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针。
在新标准下,可以使用列表初始化
int *pi=new int(1024)
//pi指向的对象的值为1024
int *pi1=new int; //默认初始化,*pi1的值未定义
int *pi2=new int(); //默认初始化,pi2为0
处于与变量初始化相同的原因,对动态分配的对象进行初始化通常是个好主意;
类似其他任何const对象,一个动态分配的const对象必须进行初始化。
释放动态内存
delete
delete p; //p必须指向一个动态分配的对象或是一个空指针
由内置指针(而不是智能指针)管理的动态内存在被显示释放前一直都会存在。
在delete之后,指针就编程了空悬指针,即,指向一块曾经保存数据对象但现在已经无效的内存的指针。
使用new和delete管理动态内存存在三个常见问题:
- 忘记delete内存
- 使用已经释放掉的对象
- 同一块内存释放两次
用new返回的指针来初始化智能指针
shared_ptr<int> p1(new int(1023)) //使用直接初始化
使用一个内置指针来访问一个智能指针所负责的对象是很危险的,因为我们无法知道对象何时会被销毁。
get用来将指针的访问权限传递给代码,你只有在确定代码不会delete指针的情况下才能使用get。特别是,永远不要用get初始化另一个智能指针或者为另一个智能指针赋值
为了正确使用智能指针,我们必须坚持一些基本规范:
1 不适应相同的内置指针初始化(或reset)多个智能指针;
2 不delete get()返回的指针
3 不使用get()初始化或reset另一个智能指针
4 如果你使用get() 返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了
5 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器
3 unique_ptr
某个时刻只能有一个unique_ptr指向一个给定对象,当unique_ptr被销毁时,它所指向的对象也被销毁
unique_ptr<int> p2(new int(42))
//p2指向一个值为42的int
由于一个unique_ptr拥有它指向的对象,因此它不支持普通的拷贝或赋值操作
unique_ptr操作
unique_ptr<T> u1 |
空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放它的指针;u2会使用一个类型为D的可调用对象来是否它的指针 |
unique_ptr<T,D> u2 |
|
unique_ptr<T,D> u(d) |
空unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete |
u=nullptr |
是否u指向的对象,将u置为空 |
u.release() |
u放弃对指针的控制权,返回指针,并将u置为空 |
u.reset() |
释放u指向的对象 |
u.reset(q) |
如果提供了内置指针q,另u指向这个对象;否则将u置为空 |
u.reset(nullptr) |
调用release会切断unique_ptr和它原理管理的对象间的联系。release返回的指针通常被用来初始化另一个智能指针,或给另一个智能指针赋值
4 weak_ptr
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。
weak_ptr <T> w |
空weak_ptr可以指向类型为T的对象 |
weak_ptr<T> w(sp) |
与shared_ptr sp指向相同类型的weak_ptr。T必须能转换为sp指向的类型 |
w=p |
p可以是一个shared_ptr或一个weak_ptr。赋值后w与p共享对象 |
w.reset() |
将w置为空 |
w.use_count() |
与w共享对象的shared_ptr的数量 |
w.expired() |
若w.use_count()为0,返回true,否则返回false |
w.lock() |
如果expired未true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr |
当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它:
auto p =make_shared<int> (42);
weak_ptr<int> wp(p);
//wp弱共享p;
由于对象可能不存在,不能使用weak_ptr直接访问对象,必须调用lock。
5 动态数组
一次性为很多元素分配内存的方法:
(1) new,分配并初始化一个对象数组
(2) allocator,允许将分配和初始化分离
使用容器的类可以使用默认版本的拷贝、赋值和析构操作。分配动态数组的类必须定义自己的操作,在拷贝、赋值以及销毁对象时管理所关联的内存。
new分配要求数量的对象并返回指向第一个对象的指针
int *pia = new int[get_size()];
//pia指向第一个int
分配一个数组会得到一个元素类型的指针。
动态数组不是数组类型。
int *pia = new int[10];
//10个未初始化的int
int *pia2 = new int[10]();
//10个值初始化为0的int
释放动态数组
delete p;
//p必须指向一个动态分配的对象或为空
delete [] pa;
//pa必须指向一个动态分配的数组或为空
智能指针和动态数组
unique_ptr<int[]> up(new int[10]);
up.release(); //自动用delete[] 销毁其指针
类型说明符中的方括号(<int[]>)指出up指向一个int数组而不是一个int。
指向数组的unique_ptr
指向数组的unique_ptr不支持成员访问运算符(.和->) |
|
其他的unique_ptr操作不变 |
|
unique_ptr<T[]> u |
u可以指向一个动态分配的数组,数组元素类型为T |
unique_ptr<T[]> u(p) |
u指向内置指针p所指向的动态分配的数组。p必须能转换为类型T* |
u[i] |
返回u拥有的数组中位置i处的对象u必须指向一个数组 |
shared_ptr未定义下标运算符
allocatoe类
将内存分配和对象构造分离;在memory库中;将内存分配和对象构造分离开来;提供一种类型感知的内存 分配方法,它分配的内存是原始的、未构造的。
allocator<string> alloc;
//可以分配string的allocator对象
auto const p = alloc.allocate(n);
//分配n个未初始化的string
标准allocator类及其算法
allocator<T> a |
定义了一个名为a的allocator对象,它可以为类型为T的对象分配内存 |
a.allocate(n) |
分配一段原始的、为构造的内存,保存n个类型为T的对象 |
a.deallocate(p,n) |
释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象;p必须是一个先前由allocate返回的指针,且n必须是p创建时所要求的大小。在调用deallocate之前,用户必须对每个在这块内存中创建的对象调用destroy |
a.construct(p,args) |
p必须是一个类型为T*的指针,指向一块原始内存;arg被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象 |
a.destroy(p) |
p为类型T*指针,此算法对p指向的对象指向析构函数 |
为了适应allocate返回的内存,必须用construct构造对象,使用未构造的内存,其行为是未定义的。
只能对真正构造了的元素进行destroy操作
标准库还未allocator类定义了两个伴随算法,可以在未初始化内存中创建对象
uninitialized_copy(b,e,b2)
以上是关于动态内存的主要内容,如果未能解决你的问题,请参考以下文章