C++之动态内存及智能指针重点总结

8 篇文章 0 订阅

C++之 动态内存

动态内存在C++中占据着很重要的地位,本文主要对动态内存和智能指针相关的一些重点进行总结。
局部static对象在第一次使用前分配,在程序结束时销毁。局部自动对象在第一次使用前分配,在程序结束时销毁。全局对象在程序启动时分配,在程序结束时销毁。
程序用堆来存储动态分配的对象-即那些在程序运行时分配的对象。动态对象的生存期由程序控制,也就是说,当动态对象不再使用时,必须显式的销毁他们。

1.动态内存和智能指针

  • 为了更容易的使用动态内存,新的标准库提供了两种智能指针类型来管理动态对象。智能指针都定义在头文件memory中
  • 默认初始化的智能指针保存着一个空指针。
  • shared_ptr和unique_ptr都支持的操作 参见C++P401 其中swap(p,q) p.swap(q) 交换p和q中的指针

1.1 shared_ptr

  • 最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数,当要用make_shared时,必须指定想要创建的对象的类型,定义方式与模板相同。

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

  • 使用动态内存的常见原因是允许多个对象共享相同的状态。

我们为strblob定义了一个名为check的private工具函数,它检查一个给定索引是否在合法范围内,除了索引,check还接受一个string参数,他会将此参数出传递给异常处理程序,这个string描述了错误内容。

  • 拷贝一个shared_ptr会递增其引用计数;将一个shared_ptr赋予给另一个shared_ptr会递增赋值号右侧shared_ptr的引用计数,而递减左侧shared_ptr的引用计数。
  • C++语言定义了两个运算符来分配和释放内存。运算符new分配内存,delete释放new分配的内存。相比于智能指针,这两种运算符管理内存非常容易出错。

值初始化只需在类型名后面加一对空括号即可。而默认初始化不需要加东西。
出于与变量初始化相同的原因,对动态分配的对象进行初始化农场是个好主意。

  • 由于编译器要用初始化器的类型来推断分配的类型,只有当括号中仅有单一初始化器时才可以使用auto
  • 定位new表达式允许我们向new传递额外的参数
  • 我们传递给delete的指针必须是指向动态分配的内存,或者是一个空指针。
    由内置指针管理的动态内存在被显示释放前一直都会存在。
  • 我们不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化的形式。
  • 使用一个内置指针来访问一个智能指针所负责的对象是很危险的,因为我们无法知道对象何时会被销毁。也不要用get初始化另一个智能指针或为智能指针赋值。get是用来将指针的访问权限传递给代码,只有在确定代码不会delete指针的情况下,才能使用get。特别是,永远不要用get初始化另一个只智能指针或者为另一个智能指针赋值。
  • 智能指针要注意的陷阱:
    (1)不使用相同的内置指针值初始化多个智能指针
    (2)不delete get() 返回的指针
    (3)不使用get() 初始化或reset另一个智能指针
    (4)如果你使用了get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了
    (5)如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器

1.2 unique_ptr

unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能得到释放。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。

  • 与shared_ptr不同,没有类似make_shared的标准库函数返回一个shared_ptr当我们定义一个unique_ptr时需要将其绑定到一个new返回的指针上。类似shared_ptr,初始化unique_ptr必须使用直接初始化

由于一个unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或者赋值操作。因此如果希望将p2初始化为p1,则不能使用unique_ptr<T>p2(p1);,因为unique_ptr不支持拷贝和赋值;正确的做法是unique_ptr<T>p2(p1.release());这里p2被初始化为p1原来保存的指针,而p1被置位空。
但是要注意的是:不能拷贝unique_ptr的规则有一个例外:我们拷贝或者复制一个将要被销毁的unique_ptr,最常见的例子是从函数返回unique_ptr

  • unique_ptr操作
unique_ptr<T> u1 和unique_ptr<T> u2(D)
//空unique_ptr,可以指向类型为T的对象,u1会使用delete来释放它的指针。u2会使用一个类型为D的可调用对象来释放它的指针
unique_ptr<T> u(d) 空unique_ptr指向类型为T的对象用类型为D的对象d来代替dlete
u=nullptr 释放u指向的对象,将u置为空
u.release() u放弃对指针的控制权,返回指针,并将u置为空
u.reset() u.reset(q) u.reset(nullptr)
// 释放u指向的对象,如果提供了内置指针,令u指向这个对象,否则将u置为空。

1.3 weak_ptr

  • 标准库还定义了一个名为weak_ptr的伴随类,他是一种弱引用,指向shared_ptr所管理的对象。和其他两个智能指针一样都定义在memory中。将一个weak_ptr绑定到一个由shared_ptr不会改变shared_ptr的引用计数,一旦最后一个指向对象的shared_ptr被销毁,对象即被释放,充分体现了弱共享对象的特点。
  • 当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它,如下所示:
auto p=make_shared<int> (42);
weak_ptr<int> wp(p);
  • 由于对象可能不存在,因而我们不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查指向的对象是否扔存在,如果存在lock返回一个指向共享对象的shared_ptr

2.动态数组

2.1 new和数组

大多数应用应该使用标准库容器而不是动态分配的数组。使用容器更为简单、更不容易出现内存管理错误并且可能有更好的性能。

  • 为了让new分配一个对象数组,我们要在类型名之后跟一对方括号,在其中表明要分配对象的数目。
int *pia=new int [get_size()];//pia指向第一个int 方括号中的大小必须是整型但不必是常量。
typedef int arrT[42]
int *p=new arrT; //分配一个42个int的数组,p指向第一个int 这里虽然没有方括号,但是由于使用了类型别名,所以实际在编译器执行这个表达式时还是使用了new[]
  • 要记住我们所说的动态数组并不是数组类型,这是非常重要的,因而我们不能对动态数组调用begin和end,也不能用范围for语句来处理动态数组中的元素。
  • new分配的对象,无论是单个的还是数组的,都是默认初始化的,可以对数组中的元素进行值初始化,方法是在大小后面接应对空的小括号。
  • 当我们使用new分配一个大小为0的数组时,new返回一个合法的非空指针。此指针保证与new返回的其他任何指针都不相同。因而我们不能创建一个大小为0的静态数组对象,但当n等于0时,调用new[n]是合法的

为了释放动态数组,我们使用一种特殊形式的delete-指针前面加上一个空方括号对 (!很重要)

delete p;//p必须指向一个动态分配的对象或为空
delete [] pa;//pa必须指向一个动态分配的数组或为空
  • 标准库提供了一个可以管理new分配的数组的unique_ptr版本。为了用一个unique_ptr管理动态数组,我们必须在对象类型后面跟一对空方括号
    unique_ptr <int []> up(new int[10])
  • 指向数组的unique_ptr不支持成员访问运算符(点和箭头运算符),因为它指向的是一个数组而不是单个对象,但是我们可以使用下标运算符来访问数组中的元素。
  • 为了访问数组中的元素,必须用get获取一个内置指针,然后用它来访问数组元素

2.2 allocator

标准库allocator类及其算法

allocator<T> a 定义了一个名为allocator对象,它可以为类型为T的对象分配内存
a.allocate(n) 分配一段原始的未构造的,保存n个类型为T的对象
a.deallocate(p,n) 释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象
a.construct(p,args) p必须是一个类型为T*的指针,指向一块原始内存,arg被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象
a.destory(p) p为T*类型的指针,此算法对p指向的对象执行析构函数

为了使用allocate返回的内存,我们必须使用construct构造对象。使用未构造的内存,其行为是未定义的。在我们用完对象后必须对每个构造函数调用destroy来摧毁它们,我们只能对真正构造了的元素进行destory操作。

3.使用标准库:文本查询程序

开始一个程序的设计的一种好的方法是列出程序的操作,了解需要哪些操作会帮助我们分析出需要什么样的数据结构,从需求入手,我们的文本查询程序需要完成什么任务,然后找出需要的标准库。

当我们设计一个类时,在真正实现成员之前先编写程序使用这个类,是一种很常用的方法,通过这种方法可以看到类是否具有我们所需要的操作。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值