C++ primer摘要(10)---动态内存

动态内存

概述

  • 我们编写的程序中所使用的对象都有严格定义的生存期
  • 全局对象在程序启动时分配,在程序结束时销毁
  • 局部自动对象,在程序进入其定义所在的程序块时被创建,在离开块时销毁
  • 局部static对象在第一次使用前分配,在程序结束时销毁
  • 除了自动和static对象外,C++还支持动态分配对象(new),动态分配的对象的生存期与它们在哪无关,只有当显示的被释放时,这些对象才会销毁
  • 动态对象的正确释放被证明是编程中极易出错的地方,为了更安全的使用动态对象,标准库定义了两个智能指针类型来管理动态分配的对象,当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它
  • 静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外地遍历
  • 栈内存用来保存定义在函数内地非static对象
  • 分配在静态或栈内存中地对象由编译器自动创建和销毁
  • 对于栈对象,仅在其定义地程序块运行时才存在
  • static对象在使用之前分配,在程序结束时销毁
  • s除了静态内存和栈内存,每个程序还拥有一个内存池,这部分内存被称作自由空间(),程序用堆来存储动态分配的对象
  • 当动态对象不再使用时,我们的代码必须显式地销毁它们

动态内存与智能指针

  • 在C++中,动态内存地管理是通过一对运算符来完成地:new delete
  • 在尚有指针引用内存地情况下进行释放,就会产生引用非法内存地指针
  • 新的标准库提供了两种智能指针类型来管理动态对象
- [x] 智能指针地行为类似常规指针,重要的区别是它负责自动释放所指向的对象
- [x] `shared_ptr`允许多个指针指向同一个对象
- [x] `unique_ptr`则`独占`所指向的对象
- [x] `weak_ptr`是一个伴随类,它是一种弱引用,指向`shared_ptr`所管理的对象
- [x] 上述三种类型都定义在`memory`头文件中
  • 如果有可能,优先使用类的实例,其次万不得已使用std::unique_ptr,再万不得已使用shared_ptr
shared_ptr类
  • 类似vector,智能指针也是模板,因此,当我们创建一个智能指针时,必须额外提供信息----指针可以指向的类型
shared_ptr<string> p1;  //shared_ptr可以执行string
shared_ptr<list<int>> p1;  //shared_ptr可以执行int的list
  • make_shared函数
- [x] 最安全的分配和使用动态内存的方法是调用一个名为`make_shared`的标准库函数
int num = 20;
shared_ptr<int> temp_ptr = make_shared<int>(num);
//通常使用以下这种更加简单的方式
auto temp_ptr = make_shared<int>(num);
  • 我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用技术,无论何时我们拷贝一个shared_ptr,计数器都会增加,当我们给shared_ptr赋予一个新值或是shared_ptr被销毁时,计数器就会递减
  • 一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象
auto r = make_shared<int>(42);  //r指向的int只有一个引用者
r = q;  //给r赋值,令它指向另一个地址
        //递增q指向的对象的引用计数
        //递减r原来指向的对象的引用计数
        //r原来指向的对象已没有引用者,会自动释放
  • 到底是用一个计数器还是其他数据结构来记录有多少指针共享对象,完全由标准库的具体实现来决定,关键是智能指针类能记录多少个shared_ptr指向相同的对象,并能在恰当的时候自动释放对象
  • 当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象,它是通过另一个特殊的成员函数析构函数完成销毁工作的,类似于构造函数,每个类都有一个析构函数,就像构造函数控制初始化一样,析构函数控制此类型对象销毁时做什么操作
  • shared_ptr自动销毁所管理的对象
- [x] `shared_ptr`的析构函数会递减它所指向的对象的引用计数,如果引用计数变为0,`shared_ptr`就会销毁对象,并释放它占用的内存
  • shared_ptr自动释放相关联的内存
- [x] 当动态对象不再被使用时,`shared_ptr`类会自动释放动态对象,这一特性使得动态内存的使用变得非常容易
  • 由于在最后一个shared_ptr销毁前内存都不会释放,保证shared_ptr在无用之后不再保留就非常重要,如果你忘记了销毁程序不再需要的shared_ptr,程序仍会正确执行,但会浪费内存
  • 如果你将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素
直接管理内存
  • C++语言定义了两个运算符来分配和释放内存
- [x] 运算符new分配内存
- [x] 运算符delete释放new分配的内存
  • 相对于智能指针,使用这两个运算符管理内存风险较大
  • deelte表达式也执行两个动作
- [x] 销毁给定的指针指向的对象
- [x] 释放对应的内存
  • 传递给deelte的指针必须指向动态分配的内存,或者是一个空指针,释放一块并非new分配的内存,或者将相同的指针释放多次,其行为是未定义的
  • 虽然一个const对象的值不能被改变,但它本身是可以被销毁的,如同任何其他动态对象一样,想要释放一个const动态对象,只要delete指向它的指针即可
  • 动态对象的生存期直到被释放为止
- [x] 由`shared_ptr`管理的内存在最后一个`shared_ptr`销毁时会被自动释放
- [x] 对于通过内置指针类型来管理的动态对象而言,直到被显式释放之前它都是存在的
  • 返回指向动态内存的指针(不是智能指针)的函数给其调用者增加了一个额外负担,即调用者必须记得释放内存
  • 使用new和delete管理动态内存存在的问题
- [x] 忘记delete内存。忘记释放动态内存会导致人们常说的`内存泄露`问题,因为这种内存永远不可能被归还给自由空间了。查找内存泄漏错误错误是非常困难的,因为通常应用程序运行很长时间后,真正耗尽内存时,才能检测到这种错误
- [x] 使用已经释放掉的对象。通过在释放内存后将指针置为空,有时可以检测出这种错误
- [x] 同一块内存释放两次。当有两个指针指向相同的动态分配对象时,可能发生这种错误。如果对其中一个指针进行了delete操作,对象的内存就被归还给自由空间了。如果随后又delete了第二个指针,自由空间就可能被破坏
- [x] 坚持使用智能指针,就可以避免所有这些问题,对于一块内存,只有在没有任何智能指针指向它的情况下,智能指针才会自动释放它
  • 在指针关联的内存被释放掉之后,如果我们需要保留指针,可以在delete之后将nullptr赋予指针,这样就清楚的指出指针不指向任何对象
shared_ptr和new的结合使用
shared_ptr<double> p1;  //p1指向一个double
shared_ptr<int> p2(new int(42));    //p2指向一个值为42的int
  • 不要混合使用普通指针和智能指针
- [x] shared_ptr可以协调对象的析构,但这仅限于其自身的拷贝(也就是shared_ptr)之间,这也是为什么推荐使用`make_shared`而不是`new`的缘故
- [x] 当将一个shered_ptr绑定到一个普通指针时,我们就将内存的管理责任交给了这个shared_ptr,一旦这样做了,就不应该再使用内置指针来访问shared_ptr所指向的内存了
- [x] 使用一个内置指针来访问一个智能指针所负责的对象是很危险的,因为我们无法直到对象何时会被销毁
  • 不要使用get初始化另一个智能指针或为智能指针赋值
- [x] 智能指针类型定义了一个名为get的函数,它返回一个内置指针,指向智能指针管理的对象
- [x] 此函数是为了这样一种情况而设计的:我们需要向不能使用智能指针的代码传递一个内置指针,使用get返回的指针的代码不能delete此指针
  • get用来将指针的访问权限传递给代码,你只有在确定代码不会delete指针的情况下,才能使用get,特别是,永远不要用get初始化另一个智能指针或为另一个智能指针赋值
  • 可以用reset来将一个新的指针赋予一个shared_ptr
p = new int(1024);  //错误,不能将一个指针赋予shared_ptr
p.reset(new int(1024)); //正确,p指向一个新对象
稚嫩指针和异常
  • 简单的确保资源被释放的方法是使用智能指针
  • 智能指针可以提供对动态分配内存安全而又方便的管理,但这建立在正确使用的前提下,以下是使用智能指针的规范
- [x] 不使用相同的内置指针初始化(或reset)多个之恶能指针
- [x] 不delete() get()返回的指针
- [x] 不使用get()初始化或reset另一个智能指针
- [x] 如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了
- [x] 如果你使用智能指针管理的资源不是new分配的内存,记得传递给他一个删除器
unique_ptr
  • 每一个unique_ptr都完全拥有它所指向的对象,与shared_ptr不同,某个时刻智能有一个unique_ptr指向一个给定对象
  • unique_ptr被销毁时,它所指向的对象也被销毁
weak_ptr
  • weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象
  • 将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象也还是会被释放
  • 当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它

动态数组

  • C++语言和标准库提供了两种一个对象数组的方法
- [x] new 
- [x] allocator
  • 大多数应用应该使用标准库容器而不是动态分配的数组,使用容器更为简单,更不容易出现内存管理错误并且可能有更好的性能
new和数组
  • 为了让new分配一个对象数组,我们要在类型名之后跟一对方括号,在其中指明要分配对象的数目
int * pia = new int[get_size()];    //pia指向第一个int
                                    //方括号中的大小必须是整型,但不必是常量
  • 也可以用一个表示数组类型的类型别名
typedef int arrT[42];   //arr表示42个int的数组类型
int * p = new arrT;     //分配一个42个int的数组,p指向第一个int
                        //在本例中,new分配一个int数组,并返回指向第一个int的指针,即使这段代码中没有方括号,编译器执行这个表达式的时候还是会用new[]
                        //int * p = new int[42];
  • 分配一个数组会得到一个元素类型的指针
- [x] 当用new分配一个数组时,我们并未得到一个数组类型的对象,而是得到一个数组元素类型的指针
- [x] 即使我们使用类型别名定义了一个数组类型,new也不会分配一个数组类型的对象
- [x] 由于分配的内存并不是一个数组类型,因此不能对动态数组调用`begin`或`end`,这些函数使用数组维度来返回指向首元素和尾后元素的指针
  • 动态数组并不是数组类型
  • 初始化动态分配对象的数组
int * pia = new int[10];            //10个未初始化的int
int * pia2 = new int[10]();         //10个值初始化为0的int
string * psa = new string(10);      //10个空string
string * psa2 = new string[10]();   //10个空string
//******下述为初始化器初始化******//
//10个int分别用列表中对应的初始化器初始化
int * pia3 = new int[10]{0,1,2,3,4,5,6...};
//10个string,前四个用给定的初始化器初始化,剩余的进行值初始化
string * psa3 = new string[10]{"a","an","the",string(3,'x')}
  • 动态分配一个空数组是合法的
char arr[0];                //错误,不能定义长度为0的数组
char * cp = new char[0];    //正确,但cp不能解引用
  • 释放动态数组
delete p;       //p必须指向一个动态分配的对象或为空
delete [] pa;   //pa必须指向一个动态分配的数组或为空
  • 在释放一个数组指针时必须使用方括号
  • unique_ptr支持管理动态数组
unique_pre<int []> up(new int[10]);
up.release();
  • shared_ptr不支持管理动态数组,如果希望使用shared_ptr管理动态数组,必须提供自己定义的删除器
shared_ptr<int> sp(new int[10] , [](int * p)(delete[] p;));   //为了使用shared_ptr,必须提供一个删除器
sp.reset();         //使用我们提供的lambda释放数组,它使用delete[]
  • shared_ptr未定义下标运算符,而且智能指针类型不支持指针算数运算,为了访问数组中的元素,必须get()一个内置指针,然后用它来访问数组元素
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357