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 p1;  //shared_ptr可以执行string
shared_ptr> p1;  //shared_ptr可以执行int的list
  • make_shared函数
- [x] 最安全的分配和使用动态内存的方法是调用一个名为`make_shared`的标准库函数
int num = 20;
shared_ptr temp_ptr = make_shared(num);
//通常使用以下这种更加简单的方式
auto temp_ptr = make_shared(num);
  • 我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用技术,无论何时我们拷贝一个shared_ptr,计数器都会增加,当我们给shared_ptr赋予一个新值或是shared_ptr被销毁时,计数器就会递减
  • 一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象
auto r = make_shared(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 p1;  //p1指向一个double
shared_ptr 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 up(new int[10]);
up.release();
  • shared_ptr不支持管理动态数组,如果希望使用shared_ptr管理动态数组,必须提供自己定义的删除器
shared_ptr sp(new int[10] , [](int * p)(delete[] p;));   //为了使用shared_ptr,必须提供一个删除器
sp.reset();         //使用我们提供的lambda释放数组,它使用delete[]
  • shared_ptr未定义下标运算符,而且智能指针类型不支持指针算数运算,为了访问数组中的元素,必须get()一个内置指针,然后用它来访问数组元素

你可能感兴趣的:(C++ primer摘要(10)---动态内存)