• 对象的生存期

    • 全局对象:程序启动时创建,程序结束时销毁
    • 局部static对象:第一次使用前创建,程序结束时销毁
    • 局部自动对象:定义时创建,离开定义所在程序块时销毁
    • 动态对象:生存期由程序控制,在显式创建时创建,显式销毁时销毁
  • 动态对象的正确释放极易出错。为安全使用动态对象,标准库定义了智能指针来管理动态对象
  • 内存空间:
    • 静态内存:局部static对象、类static数据成员、定义在任何函数之外的变量
    • 栈内存:定义在函数内的非static对象
    • 堆内存:动态对象,即运行时分配的对象
  • 静态内存和栈内存中的对象由编译器创建和销毁,堆内存中的动态对象的生存期由程序控制

12.1 动态内存与智能指针

C++通过一对运算符管理动态内存:

  • new算符在动态内存中为对象分配空间并返回指向该对象的指针,可选择对对象初始化
  • delete算符接受一个动态对象的指针,销毁该对象并释放内存
  • 确保在正确时间释放内存很难:
    • 忘记释放内存,会产生内存泄露
    • 若还有指针引用内存的情况下就将其释放,会产生引用非法内存的指针
  • C++11标准库中提供了两种智能指针来管理动态对象,负责自动释放所指向的对象,定义于memory头文件:
    • shared_ptr允许多个指针指向同一对象
    • unique_ptr指针独占所指向对象
    • weak_ptr是伴随类,是一种弱引用,指向shared_ptr管理的对象

12.1.1 shared_ptr类

  • 智能指针也是模板类,创建时必须在模板参数中给定其指向的类型
  • 默认初始化的智能指针中保存空指针,条件判断中使用智能指针是判断其是否为空
  • 解引用智能指针返回其指向的对象
shared_ptr<string> p1;
shared_ptr<list<string>> p2; if(p1 && p1->empty()) //若p1不为空,检查它是否指向一个空string
*p1="hi"; //若p1为空,解引用,将新值赋给它

make_shared 函数

  • 最安全的分配和使用动态内存的方法是调用make_shared函数,该函数定义于memory头文件中,它在动态内存中分配一个对象并初始化,返回指向它的shared_ptr
  • make_shared函数用法:
    • 是模板函数,使用时必须在模板参数中给出构造对象的类型
    • 其参数必须与构造对象的构造函数参数匹配,使用这些参数构造对象
    • 若不给实参,则对象值初始化

shared_ptr 的拷贝和赋值

  • 对shared_ptr进行拷贝/赋值时,每个shared_ptr会记录有多少个其他shared_ptr指向相同对象
  • 每个shared_ptr都有一个关联的计数器,称为引用计数
    • 一个shared_ptr的一组拷贝之间共享“引用计数管理区域”,并用原子操作保证该区域中的引用计数被互斥地访问
    • 互相独立的shared_ptr维护的引用计数也互相独立,即使指向同一对象。因此需避免互相独立的shared_ptr指向同一对象
  • 改变引用计数
    • 递增:拷贝shared_ptr时,包括:用一个shared_ptr初始化另一个shared_ptr、作为参数传入函数、作为返回值从函数传出
    • 递减:给shared_ptr赋新值、shared_ptr被销毁(例如离开作用域)
    • 一旦shared_ptr的计数器变为0,会自动释放管理的对象
  • C++标准并未要求使用计数器实现引用计数,其实现取决于标准库的实现

shared_ptr 自动销毁所管理的对象,自动释放相关联的内存

  • 指向对象的最后一个shared_ptr被销毁时,shared_ptr会通过它的析构函数完成对象的销毁
  • 析构函数控制此类型对象销毁时的操作,一般用于释放对象的资源。shared_ptr类型的析构函数被调用时递减引用计数,一旦计数为0即销毁对象。
  • 必须确保shared_ptr在不使用时及时删除。例如容器中的shared_ptr在不使用时要erase
//创建对象并返回智能指针
shared_ptr<Foo> factory(T arg){
return make_shared<Foo>(arg); //创建时计数为1,传出拷贝+1,离开作用域-1
} //使用factory创建对象,使用完后销毁
void use_factory(T arg){
shared_ptr<Foo> p=factory(arg); //创建对象,初始引用计数为1
/* 使用p */ //未传出,离开作用域时计数-1变为0,对象被销毁
} //使用factory创建对象,使用完后不销毁
shared_ptr<Foo> use_factory(T arg){
shared_ptr<Foo> p=factory(arg); //创建对象,初始引用计数为1
/* 使用p */
return p; //传出时拷贝+1,离开作用域-1,传出后计数为1
}

使用了动态生存期的资源的类

  • 使用动态内存的3种情况

    • 不知道需要使用多少对象(容器)
    • 不知道所需对象的准确类型(多态)
    • 需在多个对象间共享数据
  • 若两个对象共享底层数据,则某个对象被销毁时不可单方面销毁底层数据。此时应将共享的数据做成对象,在需共享它的两个类内分别用shared_ptr访问
  • 对类对象使用默认版本的拷贝/赋值/销毁操作时,这些操作拷贝/赋值/销毁类的数据成员(包括智能指针)。
//定义StrBlob类
class StrBlob{
public:
//定义类型
using size_type=vector<string>::size_type;
//两个构造函数,默认初始化和列表初始化
StrBlob();
StrBlob(initializer_list<string> il);
//以下是对底层vector操作的封装
size_type size() const {return data->size();}
bool empty() const {return data->empty();}
void push_back(const string &t) {data->push_back(t);}
void pop_back();
string &front();
string &back();
private:
//用shared_ptr管理底层的vector<string>数据
shared_ptr<vector<string>> data;
//检查索引i是否越界,越界时用msg抛出异常
void check(size_type i, const string &msg) const;
};
//默认构造函数,底层vector<string>默认初始化,返回shared_ptr用于初始化data
StrBlob::StrBlob():
data(make_shared<vector<string>>())
{}
//构造函数,底层vector<string>列表初始化,返回shared_ptr用于初始化data
StrBlob::StrBlob(initializer_list<string> il):
data(make_shared<vector<string>>(il))
{}
//检查下标是否越界
void StrBlob::check(size_type i, const string &msg) const {
if(i>=data->size())
throw out_of_range(msg);
}
//以下3个函数分别实现front、back、pop_back操作,用0来check索引判断是否为空
string &StrBlob::front(){
check(0,"front on empty StrBlob");
return data->front();
}
string &StrBlob::back(){
check(0,"back on empty StrBlob");
return data->back();
}
void StrBlob::pop_back(){
check(0,"pop_back on empty StrBlob");
data->pop_back();
}
/* 使用StrBlob */
StrBlob b1; //创建新StrBlob
{ //进入新作用域
StrBlob b2={"a","an","the"}; //初始化b2
b1=b2; //用b2初始化b1,它们共享底层数据
} //离开作用域,b2被释放,b1仍存在,共享的底层数据未丢失
while(b1.size()>0){
cout<<b1.back()<<endl;
b1.pop_back();
}

12.1.2 直接管理内存

  • 两个运算符分配/释放动态内存:

    • new分配内存,并构造对象
    • delete销毁对象,并释放内存
  • 使用new/delete管理动态内存的类不能依赖动态对象成员的拷贝/赋值/销毁的任何默认操作
  • 堆内存中分配的空间是匿名的,故new无法为其分配的对象命名,只能返回一个指向该对象的指针
  • 动态对象初始化
    • 默认情况下用默认初始化:内置类型的值未定义,类类型依赖默认构造函数

    • 直接初始化:用圆括号调用构造函数,或花括号列表初始化

    • 值初始化:类型名后跟一对空的圆括号。对于有默认构造函数的类类型而言,值初始化没有意义(都是调用默认构造函数),但对于内置类型值初始化可有良好定义的值

    • 拷贝初始化:使用圆括号里放单一对象,被分配的对象用它初始化。此时可用auto推导需分配的类型

      //默认初始化
      int *pi=new int; //未定义
      string *ps=new string; //默认初始化为空字符串
      //直接初始化
      int *pi=new int(1024); //初始化为1024
      string *ps=new stirng(10,'9'); //初始化为"9999999999"
      vector<int> *pv=new vector<int>{0,1,2,3}; //初始化为{0,1,2,3,4,5}
      //值初始化
      int *pi=new int(); //初始化为0
      string *ps=new string(); //初始化为空字符串
      //拷贝初始化
      auto p1=new auto(obj); //用obj拷贝初始化p1
      auto p2=new auto{a,b,c}; //错,只能拷贝初始化为auto

动态分配的 const 对象

  • 用new分配const对象是合法的,const对象必须初始化。
const int *pci = new const int(1024);
const string *pci = new const string;

内存耗尽

  • 若new不能分配要求的空间,则抛出名为bad_alloc的异常。
  • 可向new算符传参来阻止抛出异常,传递了参数的new叫定位new
  • 向new传入std::nothrow,则它不会抛出异常。若不能分配内存,则返回空指针。
  • bad_alloc和nothrow都定义在头文件new
int *p1 = new int; //如果分配失败,new抛出std::bad_alloc
int *p2 = new (nothrow) int; //如果分配失败,new 返回空指针

释放动态内存

  • delete表达式将内存归还给系统,它接受一个指针,指向需要释放的对象
  • delete表达式执行两个工作:
    • 销毁指针指向的对象
    • 释放对应的内存

指针值和 delete

  • 递给delete表达式的指针必须指向动态内存,或是空指针
  • 用delete释放非new分配的内存,或者将同一指针释放多次,都是未定义
  • 编译器无法知道一个指针是否指向动态内存,也无法知道一个指针指向的内存是否已被释放,故这些错误不会被编译器发现
  • const对象的值不可改变,但可被销毁
int i, *pil = &i, *pi2 = nullptr;
double *pd = new double(33), *pd2 = pd;
delete i; //错误,i不是指针
delete pil; //未定义,pil指向一个局部变量
delete pd; //正确
delete pd2; //未定义,pd2指向的内存已经被释放
delete pi2; //正确,释放一个空指针总是没有错误的 const int *pci=new const int(1024);
delete pci;

动态对象的生存期直到被释放时为止

  • 内置指针管理的动态对象,在被显式释放之前一直存在
  • 返回指向动态内存的指针的函数给其调用者增加了一个额外负担——调用者必须记得释放
Foo* factory(T arg){
return new Foo(arg);
} void use_factory(T arg){
Foo *p = factory(arg);
//使用p 但不delete它
} //p离开了它的作用域,但它所指向的内存没有被释放
  • 内置类型的对象被销毁时什么都不会发生(与类类型不一样)。特别是,内置指针被销毁时不影响其指向的对象。若这个内置指针指向动态对象,则空间不会被释放
void use_factory(T arg){
Foo *p = factory(arg);
//使用p
delete p;
} Foo* use_factory(T arg){
Foo *p = factory(arg);
//使用p
return p; //调用者必须释放内存
}

delete 之后重置指针值

  • delete之后,指针变为空悬指针,即指向一块曾经保存数据对象但现在已经无效的内存指针。避免空悬指针:

    • 尽量在指针即将离开作用域时释放其管理的动态内存
    • 也可在delete后立即将指针置为nullptr
  • delete内存后将指针置nullptr的做法只对单个指针有效,若还有其他指针指向该对象则它们变为空悬指针。由于很难知道有哪些指针指向这个对象,故很难用new和delete管理动态内存

12.1.3 shared_ptr和new结合使用

  • 可用new返回的内置指针初始化智能指针,如果不对智能指针初始化,就被初始化为空指针
  • 接受内置指针的智能指针构造函数是explicit的,即不能将内置指针隐式转换为智能指针,必须直接初始化
  • 用于初始化智能指针的内置指针必须指向动态内存,因为智能指针默认使用delete释放其指向的对象。静态内存和栈内存不需要也不能使用智能指针
shared_ptr<int> p1=new int(1024);       //错,不可隐式转换
shared_ptr<int> p2(new int(1024)); //对,可以直接构造
shared_ptr<int> clone(int p){
return new int(p); //错,不可隐式转换
}
shared_ptr<int> clone(int p){
return shared_ptr<int>(new int(p)); //对,可以直接构造
}

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

  • shared_ptr用于自动管理对象释放的功能,只限于其自身的一组拷贝之间,互相独立的shared_ptr其引用计数也互相独立内置指针不参与引用计数
  • 推荐使用make_shared而不用new的内置指针初始化shared_ptr,因为make_shared可保证分配对象的同时和shared_ptr绑定,避免将一块内存绑定到多个互相独立的shared_ptr
  • 使用内置指针构造智能指针时必须立即构造,禁止混合使用两种指针,禁止传参时构造
  • 将一个shared_ptr绑定到一个内置指针时,内存管理的责任被交给shared_ptr,不应该再用该内置指针访问内存
void process(shared_ptr<int> ptr){  //传入时copy,计数+1
/* 使用ptr */
} //离开作用域,计数-1
//以下为正确用法:
shared_ptr<int> p(new int(42)); //新建一个智能指针
process(p); //处理后引用计数为1
int i=*p; //以下为错误用法:
int *x(new int(1024));
process(x); //错,不可将内置指针隐式转换为智能指针
process(shared_ptr<int>(x)); //该智能指针的生存期只在这个函数中,离开时智能指针被释放,对象也被释放
int j=*x; //x是空悬指针

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

  • shared_ptr定义了get成员函数,它返回内置指针,指向shared_ptr管理的对象。用于不兼容shared_ptr的情形。
  • get使用风险
    • 不可将get返回的内置指针dedete,因为原来的shared_ptr变为空悬

【c++ Prime 学习笔记】第12章 动态内存的更多相关文章

  1. 《C++ Primer》笔记 第12章 动态内存

    shared_ptr和unique_ptr都支持的操作 解释 shared_ptr sp或unique_ptr up 空智能指针,可以指向类型为T的对象 p 将p用作一个条件判断,若p指向一个对象,则 ...

  2. CSS3秘笈第三版涵盖HTML5学习笔记9~12章

    第9章,装饰网站导航 限制访问,处于隐私方面考虑,浏览器已经开始限制可以对伪类:visited应用哪些CSS属性了.其中包括对已访问过的链接定义color.background-color.borde ...

  3. C++ Primer 5th 第12章 动态内存

    练习12.1:在此代码的结尾,b1 和 b2 各包含多少个元素? StrBlob b1; { StrBlob b2 = {"a", "an", "th ...

  4. 《C和指针》 读书笔记 -- 第11章 动态内存分配

    1.C函数库提供了两个函数,malloc和free,分别用于执行动态内存分配和释放,这些函数维护一个可用内存池. void *malloc(size_t size);//返回指向分配的内存块起始位置的 ...

  5. [C++ Primer] : 第12章: 动态内存

    动态内存与只能指针 静态内存用来保存局部static对象, 类static数据成员以及定义在任何函数之外的变量. 栈内存用来保存定义在函数内的非static对象. 分配在静态或栈内存中的对象由编译器自 ...

  6. 《DOM Scripting》学习笔记-——第七章 动态创建html内容

    本章内容: 1.动态创建html内容的“老”技巧:document.write()和innerHTML属性 2.DOM方法:createElement(),creatTextNode(),append ...

  7. &lt;&lt;Python基础教程&gt;&gt;学习笔记 | 第12章 | 图形用户界面

    Python支持的工具包非常多.但没有一个被觉得标准的工具包.用户选择的自由度大些.本章主要介绍最成熟的跨平台工具包wxPython.官方文档: http://wxpython.org/ ------ ...

  8. 《Jave并发编程的艺术》学习笔记(1-2章)

    Jave并发的艺术 并发编程的挑战 上下文切换 CPU通过时间片分配算法来循环执行任务,当前时间片执行完之后会切换到下一个任务.但是,切换会保存上一个任务的状态,一遍下次切换回这个任务时,可以再次加载 ...

  9. 【c++ Prime 学习笔记】目录索引

    第1章 开始 第Ⅰ部分 C++基础 第2章 变量和基本类型 第3章 字符串.向量和数组 第4章 表达式 第5章 语句 第6章 函数 第7章 类 第 Ⅱ 部分 C++标准库 第8章 IO库 第9章 顺序 ...

随机推荐

  1. 【Elasticsearch】.NetCore中Elasticsearch组件NEST的使用

    .NetCore中Elasticsearch组件NEST的使用 1. 安装Docker # 安装Docker curl -fsSL https://get.docker.com | bash -s d ...

  2. “ShardingCore”是如何针对分表下的分页进行优化的

    分表情况下的分页如何优化 首先还是要给自己的开原框架打个广告 sharding-core 针对efcore 2+版本的分表组件,首先我们来快速回顾下目前市面上分表下针对分页常见的集中解决方案 分表解决 ...

  3. SSH整合(二)

    SSH框架实现登录.新闻增删改查.树形菜单 项目结构 pom.xml 网不好不要一次引入太多,容易下不全 <project xmlns="http://maven.apache.org ...

  4. [源码解析] 深度学习流水线并行 PipeDream(4)--- 运行时引擎

    [源码解析] 深度学习流水线并行 PipeDream(4)--- 运行时引擎 目录 [源码解析] 深度学习流水线并行 PipeDream(4)--- 运行时引擎 0x00 摘要 0x01 前言 1.1 ...

  5. SQL Server数据表设计编辑后无法保存处理办法

    关于使用 SQL Server 企业管理器,表[设计]界面,修改数据表字段或类型无法保存的问题处理过程: 使用SQL Server数据库的你是否遇到过每次数据库编辑工具内点击设计修改表字段或类型要保存 ...

  6. Golang入门学习(四):常用的函数汇总

    文章目录 2.4 常用的内置函数 2.4.1 字符串常用内置函数 2.4.2 常用的时间和日期相关函数 2.4.3 内置函数 2.4 常用的内置函数 2.4.1 字符串常用内置函数 https://g ...

  7. SpingBoot-Dubbo-Zookeeper-分布式

    目录 分布式理论 什么是分布式系统? Dubbo文档 单一应用架构 垂直应用架构 分布式服务架构 流动计算架构 什么是RPC RPC基本原理 测试环境搭建 Dubbo Dubbo环境搭建 Window ...

  8. 前端框架VUE——安装及初始化

    本篇文章适合,想要学习 vue,但对 vue 又没有接触过的同学阅读,是非常基础的内容.告诉大家使用 vue 时的安装方式,及如何创建实例,展示内容. 一.安装方式 vue 是一种前端框架,所以使用前 ...

  9. Gotop安装

    一个方便的图形面板 Github:https://github.com/cjbassi/gotop 安装 git clone --depth 1 https://github.com/cjbassi/ ...

  10. 论文解读(SimCLR)《A Simple Framework for Contrastive Learning of Visual Representations》

    1 题目 <A Simple Framework for Contrastive Learning of Visual Representations> 作者: Ting Chen, Si ...