C++ Primer第五版学习 第十二章

一、三种内存

静态内存:保存局部static对象、类static数据成员以及定义在任何函数之外的变量

栈内存:保存定义在函数内的非static对象。

分配在静态内存和栈内存中的对象由编译器自动创建和销毁。

对于栈对象,仅在其定义的程序块运行时才存在。

static对象在使用之前分配,在程序结束时销毁。

动态内存:每个程序拥有的一个内存池,这部分内存被称为自由空间或堆(heap)。程序用堆来存储动态分配的对象——即,那些在程序运行时分配的对象。动态对象的生存期由程序来控制,当动态对象不再使用时,代码必须要显示将其销毁。

程序使用动态内存的原因:(常见原因是允许多个对象共享相同的状态)

1)程序不知道自己需要使用多少对象

2)程序不知道自己所需对象的准确类型

3)程序需要在多个对象间共享数据

二、动态内存和智能指针

1. 管理动态内存

new 在动态内存中为对象分配空间并返回一个指向该对象的指针,(可以选择对对象进行初始化)

delete 销毁该对象,并释放与之相关的内存。

程序使用动态内存的原因

1)程序不知道自己要使用多少对象

2)程序不知道所需对象的准确类型

3)程序需要在多个对象间共享数据

(即允许多个对象共享相同的状态)

引入智能指针的原因(动态内存的使用很容易出问题,因为确保在正确时间内释放内存是极其困难的。忘记释放内存会产生内存泄漏;还有指针引用内存的情况下释放了内存,会产生引用非法内存的指针)

2. 智能指针

为了更安全容易地使用动态内存,新的标准库提供两种智能指针类型来管理动态对象

(两种智能指针的区别在于管理底层指针的方式)

shared_ptr允许多个指针指向同一个对象。(weak_ptr指向shared_ptr所管理的对象。是一种弱引用。)

unique_ptr则独占所指向的对象。

3. shared_ptr类

类似vector, 智能指针也是模板。

默认初始化的智能指针中保存着一个空指针。

智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果就是检测它是否为空。

shared_ptr<string> p1;    //指向string
shared_ptr<list<int>> p2;    //指向int的list

//如果 p1 不为空,检查它是否指向一个空string
if (p1&& p1->empty)
    *p1 = "hi" ;
//如果p1指向一个空string, 解引用p1, 将一个新值赋予string

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 都是 shared_ptr,所保存的指针必须能相互互换。此操作会递减 p 中的引用计数,递增q的引用计数;若 p 的引用计数变为0,则将会管理的原内存释放。
p.unique() 若 p.use_count() 为1,返回 true;否则返回 false
p.use_count()返回与 p 共享对象的智能指针数量;可能很慢,主要用于调试
p.reset()放弃内部对象所有权拥有对象的变更,会引起原有对象的引用计数的减少

shared_ptr 和 unique_ptr 都支持的操作

shared_ptr<T> sp 

unique_ptr<T> up

空智能指针,可以指向类型为T的对象
p将p用作一个条件判断,若p指向一个对象,则为 true
*p解引用p,获得它指向的对象
p->mem等价于(*p).mem
p.get()返回p中保存的指针。要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了
swap(p,q) ,p.swap(q)交换 p 和 q 中的指针


   引用计数

每个shared_ptr都有一个关联的计数器,无论何时我们拷贝一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。当我们为shared_ptr赋予一个新值或是shared_ptr被销毁时,计数器就会递减。

一旦一个shared_ptr的计数器变为0, 它就会自动释放自己所管理的对象。

shared_ptr自动销毁所管理的对象

当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。它是通过析构函数完成销毁操作的。

shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,则执行析构函数。

(一般来说,如果两个对象共享底层的数据,当某个对象被销毁时,我们不能单方面地销毁底层数据)

shared_ptr还会自动释放相关联的内存

当动态对象不再被使用时,shared_ptr类会自动释放动态对象,这一特性使得动态内存的使用变得非常容易。

如果你将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的元素。

4. 直接管理内存(new/delete)

new

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

int *pi = new int;    //pi指向一个动态分配的、未初始化的无名对象

//此new表达式在自由空间构造一个int型对象,并返回指向该对象的指针。

默认情况下,动态分配的对象是默认初始化的,这意味着内置类型或组合类型的对象的值将是未定义的,而类类型对象将用默认构造函数进行初始化:(如果要进行初始化,则初始化的方式和之前的一样)

string *ps = new string;    //初始化为空string
int *pi = new int;    //pi指向一个未初始化的int

也可以对动态分配的对象进行值初始化,只需在类型名之后跟一对空括号即可。

string *ps = new string();    //初始化为空string(但是这里不加括号也可以初始化因为它是类)
int * pi = new int();    //初始化为0

如果我们使用括号进行值初始化,则同时可以使用auto,从值初始化器来推算出我们想要分配的对象的类型。

auto p1 = new auto(初始值);
auto p2 = new auto{a,b,c};    //错误,因为这个时候只能用括号并且只能有单个初始化器

动态分配的const对象:用new分配const对象是合法的,也是必须是初始化的。(对于一个定义了默认构造函数的类类型,其const动态对象可以隐式初始化,但是其他类型的对象必须显式初始化),new返回一个const指针。

内存耗尽

默认情况下,如果new不能分配所要求的内存空间,它会抛出一个类型为bad_alloc的异常。我们可以改变使用new的方式来阻止它抛出异常。

int *p1 = new int;    //如果分配失败,new抛出bad_alloc


int *p2 = new (nothrow) int;    //如果分配失败,new返回一个空指针
//这种称为定位new,允许我们向new传递额外的参数。

delete释放动态内存

为了防止内存耗尽,在动态内存使用完毕后,必须将其归还给系统。

delete表达式接受一个指针,指向我们想要释放的对象。(如果删除的不是指针,则执行会产生潜在的危害)

(但是释放一块并非new分配的内存,或者将相同的指针值释放多次,其行为是未定义的)

delete p;    //p必须指向一个动态分配的对象或是一个空指针

由内置指针管理的动态内存在被显示释放前一直都会存在。

使用常见问题:

1) 忘记delete 内存

2) 使用已经释放的对象

3) 同一块内存释放两次

delete之后重置指针值

当我们delete一个指针之后,指针值就变得无效了。虽然指针已经无效,但在很多机器上指针仍然保存着动态内存的地址。在delete之后,指针就变成了空悬指针,只想一块曾经保存数据对象但现在已经无效的内存的指针。

所以如果我们需要保留指针,可以在delete之后将nullptr赋予指针,这样就清楚地指出指针不指向任何对象。

5.  shared_ptr和new结合使用

接受指针参数的智能指针构造函数是explicit的。因此我们不能将一个内置指针隐式转换成一个智能指针,必须使用直接初始化形式来初始化一个智能指针

shared_ptr<int> p1 = new int(1024);    //错误,必须使用直接初始化形式
shared_ptr<int> p2 =(new int(1024));    //正确,使用了直接初始化形式
定义和改变shared_ptr的其他方法
shared_ptr<T> p(q)

p管理内置指针q所指向的对象;q必须指向new分配的内存,且能够转换为T*类型

shared_ptr<T> p(u)p从unique_ptr u 那里机关了对象的所有权;将u置为空
shared_ptr<T> p(q,d)p接管了内置指针q所指向的对象的所有权。q必须能转换为T*类型。p将使用可调用对象d 来代替delete
shared_ptr<T> p(p2, d)p是shared_ptr p2的拷贝, 惟一的区别是p将用可调用对象d来代替delete

p.reset()

p.reset(q)

p.reset(q,d)

若p是唯一指向其对象的shared_ptr, reset会释放此对象。若传递了可选的参数内置指针q,会令p指向q,否则会将p置为空。若还传递了参数d,将会调用d而不是delete来释放q

不要混合使用普通指针和智能指针

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

6. unique_ptr

一个unique_ptr“拥有”它所指向的对象。当unique_ptr被销毁时,它所指向的对象也被销毁。

初始化unique_ptr必须采用直接初始化的形式。

不支持普通的拷贝或赋值操作。

unique_ptr特有的操作

unique_ptr <T> u1;

unique_ptr<T,D> u2

空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放它的指针;

u2会使用一个类型为D的可调用对象来释放它的指针

unique_ptr<T,D> u(d)空unique_ptr,指向类型为T的对象,用类型为D的对象d来代替delete
u = nullptr释放u指向的对象,将u置为空
u.release()u放弃对指针的控制权,返回指针,并将u置为空

u.reset()

u.reset(q)

u.reset(nullptr)

释放u指向的对象,如果提供了内置指针q, 令u指向这个对象;否则将u置为空

可以使用release和reset将指针的使用权转移(即这就可以用作间接地拷贝)

//将所有权从p1转移至p2
unique_ptr<string> p2(p1.release);    //release将p1置为空,并返回p1之前所指的指针
unique_ptr<string> p3(new string("trans"));

p2.reset(p3.release());    //reset释放了p2原来指向的内存

6. weak_ptr

是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr上不会改变shared_ptr的计数。

当最后一个shared_ptr被释放,即使有weak_ptr,也会被释放。

weak_ptr
weak_ptr<T> w空weak_ptr可以指向类型为T的对象
weak_ptr<T> w(sp)与shared_ptr sp指向相同对象的weak_ptr。T必须能转换为sp指向的类型
w = pp可以是一个shared_ptr或一个weak_ptr。赋值后w与p共享对象
w.reset()将w置为空
w.use_count()与w共享对象的weak_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,p的引用计数不改变

由于wp指向对象可能不存在,所以不能直接使用其访问对象,需要调用lock。

shared_ptr<int> np = wp.lock()

三、动态数组(动态数组并不是数组类型)

1. new和数组

int *pia = new int[get_size()];
//返回指向对象的第一个指针

这里方括号中的大小必须是整型,不一定要是常量。

默认情况下,new分配的对象不管是单个分配的还是数组中的,都是默认初始化的。

可以对数组中的元素进行值初始化,就是在大小之后跟一对空括号。(可以进行值初始化,但是不能在括号中给出初始化器,故不能使用auto分配数组)

int *pia = new int[10];    //10个未初始化的int
int *pia = new int[10]();    //进行了值初始化

动态分配一个空数组是合法的,可以用任意表达式来确定要分配的对象的数目。

当对new分配一个大小为0的数组时,new返回一个合法的非空指针,此指针保证与new返回的其他任何指针都不相同。

释放动态数组

delete p;    //p必须指向一个动态分配的对象或为空
delete []pa;    //pa必须指向一个动态分配的数组或为空

//如果不使用[],可能不会报错,但是会产生异常

释放,销毁pa指向的数组的元素,释放相应的内存,数组中的元素按照逆序销毁。

2. 智能指针与动态数组

使用unique_ptr管理动态数组,必须在对象类型后面加一对方括号。

unique_ptr<int[]> up(new int[10]; 
//up指向一个包含10个未初始化int的数组
up.release()    //自动用delete[]销毁其指针
指向数组的unique_ptr
指向数组的unique_ptr不支持成员访问运算符(点和箭头运算符)
unique_ptr<T[ ]> uu可以指向一个动态分配的数组,数组元素类型为T
unique_ptr<T[ ]> u(p)u指向内置指针p所指向的动态分配的数组。p必须能转换为类型T*
u[ i ]返回u拥有的数组中位置 i 处的对象

标准定义上,shared_ptr是不直接支持管理动态数组的。如果希望使用shared_ptr管理一个动态数组,必须提供自己定义的删除器。

3. allocator类

定义在头文件memory中,它帮助我们将内存分配和对象构造分离开来。它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。

allocator<string> alloc;    //可以分配string的alloc对象
auto const p = alloc.allocate(14);    //分配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指向的对象执行析构函数(只能对真正构造了的函数使用)
拷贝和填充未初始化内存的算法
uninitialized_copy(b,e,b2)从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中。b2指向的内存必须足够大,能够容纳输入序列中的元素的拷贝
uninitialized_copy_n(b,n,b2)从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中
uninitialized_fill(b,e,t) 从迭代器b和e指定的原始内存范围中创建对象,对象的值均为t的拷贝
uninitialized_fill_n(b,n,t) 从迭代器b指向的内存地址开始创建n个对象。b必须指向足够大的未构造的原始内存,能够容纳给定数量的对象


    

   

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值