Chapter12:动态内存

  • 智能指针——shared_ptr

为了更容易地使用动态内存,新的标准提供了智能指针来管理动态对象。智能指针的行为类似常规指针,重要的区别是它负责自动释放指向的对象。

智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。

1 if (p1 && p1->empty())

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数

make_shared<T>(args); //返回一个shared_ptr, 指向一个动态分配的类型为T的对象,使用args初始化此对象。

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

shared_ptr在无用之后仍然保留的一种可能是:你将shared_ptr存放在一个容器中,随后重排了容器,从而不在需要某些元素。这个时候你应该确保使用erase删除那些不再需要的shared_ptr的元素。

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

(三种情况)

1. 程序不知道自己需要使用多少对象;例如容器类。

2. 程序不知道所需对象的准确类型;例如派生与继承。

3. 程序需要在多个对象间共享数据;到目前为止,我们使用过的类中,分配的资源都与对应对象生存期一致;但某些类分配的资源具有与原对象相独立的生存期,即:当某个对象被销毁时,我们不能单方面地销毁底层数据。

定义需要共享数据的类时,重点要考虑的是初始化、赋值、拷贝、销毁等拷贝控制得问题。

  • 直接管理内存:new/delete

初始化

默认情况下,动态分配的对象时默认初始化的,这意味着内置类型或组合类型的对象的值是未定义的,而类类型对象将用默认构造函数进行初始化;

对于定义了自己的构造函数的类类型来说,要求值初始化没有意义;不管采用什么形式,对象都会通过默认构造函数来初始化;

1 string *ps1 = new string; //默认初始化为空string
2 string *ps2 = new string(); //默认初始化为空string
3
4 int* pi1 = new int;//默认初始化;*pi1的值未定义
5 int* pi1 = new int(); //值初始化为0

delete

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

动态对象的生存期知道被释放时为止;

delete之后的指针值就变为无效了。虽然指针已经无效,但是很多机器上指针仍然保存着动态内存的地址。此时指针就变为空悬指针(dangling pointer),最好在delete之后将nullptr赋予指针。

动态内存的一个基本问题是:可能多个直着你指向相同的内存,而在实际系统中,查找相同内存的所有指针是异常困难的。

  • shared_ptr+new

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

1 shared_ptr<int> p1 = new int(1024);//错误,必须使用直接初始化形式
2 shared_ptr<int> p2(new int(1024));//正确,使用直接初始化形式

不要混合使用普通指针和智能指针——以防止同一块内存绑定到多个独立创建的shared_ptr上——推荐使用make_shared,而不是new。

 1 void process(shared_ptr<int> ptr)
 2 {//参数按值传递,发生拷贝操作
 3 }
 4
 5 shared_ptr<int> p(new int(42));
 6 process(p);//在process中引用计数为2
 7 int i = *p;//引用计数为1
 8
 9 int *x(new int(42));//x是普通指针
10 process(x);//错误
11 process(shared_ptr<int>(x));//会释放x的内存
12 int j = *x;

当将一个shared_ptr绑定到一个普通指针时,我们就将内存管理责任交给了这个shared_ptr。一旦这样做了,我们就不应该再使用内置指针来访问shared_ptr所指向的内存了;

使用一个内置指针来访问一个智能指针所负责的对象时很危险的,因为我们无法知道对象何时会被销毁。

不要使用get初始化另一个智能指针或为智能指针赋值——以防止同一块内存绑定到多个独立创建的shared_ptr上。

unique():在改变对象之前,我们要检查自己是否是当前对象的仅有的用户。如果不是,在改变之前,我们可能需要制作一份新的拷贝。

1 if (!p.unique())
2     p.reset(new string(*p));

智能指针使用规范

1. 不使用相同的内置指针值初始化(或reset)多个智能指针;

2. 不 delete get()返回的指针;

3. 不使用get()初始化或reset另一个智能指针;

4. 如果使用了get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变成无效了;

5. 如果你使用智能指针管理的资源不是new分配的内存,记住传递一个删除器给它。

  • 动态数组

标准库提供了一个可以管理new分配的数组的unique_ptr版本。为了用一个unique_ptr管理动态数组,我们必须在对象类型后面加一对空括号:

unique_ptr<int []> up(new int[10]>;
up.release();//自动用delete[]销毁其指针

与unique_ptr不同,shared_ptr不直接支持管理动态数组。如果希望使用shared_ptr管理动态数组,必须提供自己定义的删除器:

1 shared_ptr<int> sp(new int[10], [](int *p) {delete[] p; });
2 sp.reset();//使用lambda释放数组
1 for (size_t i = 0; i != 10; ++i)
2 {
3     up[i] = i;
4     *(sp.get() + i) = i;
5 }
  • allocator类

new有一些灵活性上的局限,其中一方面表现在它将内存分配和对象构造组合在一起。类似的,delete将对象析构和内存释放组合在一起;

当分配一大块内存时,我们通常计划在这块内存上按需构造对象。在此情况下,我们希望将内存分配和对象构造分离。这意味着我们可以分配大块内存,但只在真正需要时才执行对象创建工作。

allocator的两个伴随算法:

1 uninitialized_copy(b, e, b2);//从迭代器b和e指定的范围拷贝元素到迭代器b2指定的未构造的原始内存中。
2 uninitialized_copy_n(b, n, b2)
1 uninitialized_fill(b, e, t);//在迭代器b和e指定的原始内存范围内创建对象,值均为t的拷贝;
2 uninitialized_fill_n(b, n, t)
时间: 2024-10-25 22:14:29

Chapter12:动态内存的相关文章

C++ Primer笔记8_动态内存_智能指针

1.动态内存 C++中,动态内存管理是通过一对运算符完成的:new和delete.C语言中通过malloc与free函数来实现先动态内存的分配与释放.C++中new与delete的实现其实会调用malloc与free. new分配: 分配变量空间: int *a = new int; // 不初始化 int *b = new int(10); //初始化为10 string *str = new string(10, ); 分配数组空间: int *arr = new int[10];//分配的

浅谈C++容器动态内存管理的优化

在信息学竞赛中,C++的容器的用途非常广泛,但经常因常数过大而超时.怎样才能提高它们的效率呢? 我们知道,容器是存储同一类对象的对象,既然"对象"我们无法改变,那么我们只能从"存储"入手,不难想到,不同容器在实现上的根本区别是它们对应着不同的内存组织方式,内存管理无疑是这种实现的核心,所以优化内存管理是加快容器效率的最好途径之一. 一.内存分配器简介 怎样才能优化内存管理呢?很简单,C++为我们提供了这样的接口,我们可以通过自定义容器模板中的最后一个allocato

C++动态内存分配

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解>电子工业出版社等. CSDN视频网址:http://edu.csdn.net/lecturer/144 C / C ++中的动态内存分配是指程序员手动执行内存分配, 动态分配的内存分配给堆,非静态和局部变量获取在Stack上分配的内存.详情查看上篇博文:C程序的内存布局. 什么是应用程序? 动态分配的

【C语言天天练(九)】动态内存分配

引言:数组的元素存储于内存中连续的位置上.当一个数组被声明时.它所须要的内存在编译时就被分配. 可是,我们能够使用动态内存分配在执行时为它分配内存. 一块内存的生命周期能够分为四个阶段:分配.初始化.使用.释放. 内存的分配一般使用C函数库里的malloc函数(原型:void *malloc(size_t size)). 关于malloc函数应该注意一下几点: 1.malloc的參数就是须要分配的内存的字节数. 2.malloc所分配的是一块连续的内存. 3.分配成功.则返回指向分配内存起始地址

动态内存管理

(1).c中动态内存管理方式 malloc.calloc.realloc在堆上开辟空间.free将申请的空间释放掉 void *malloc( size_t size );      void *calloc( size_t num, size_t size );      void *realloc( void *memblock, size_t size ); (2).C++中动态内存管理 通过new和delete运算符进行动态内存管理 (3).malloc/free和new/delete的

SQLite剖析之动态内存分配

SQLite通过动态内存分配来获取各种对象(例如数据库连接和SQL预处理语句)所需内存.建立数据库文件的内存Cache.以及保存查询结果.我们做了很多努力来让SQLite的动态内存分配子系统可靠.可预测.健壮并且高效.本文概述SQLite的动态内存分配,软件开发人员在使用SQLite时可以据此获得最佳性能. 1.特性    SQLite内核和它的内存分配子系统提供以下特性:    (1)对内存分配失败的健壮处理.如果一个内存分配请求失败(即malloc()或realloc()返回NULL),SQ

【C++】动态内存与智能指针

C++常见的内存分配方式有三种: 从静态存储区分配,这里主要是存储局部static对象,类的static成员以及定义在函数之外的变量: 从栈内存分配,这里主要是存储函数内的非static对象: 从堆内存动态分配 其中,静态存储区以及栈内存中的对象,都是由编译器自动创建和销毁,而堆内存中的对象都是由程序显示控制的,通常都是new创建delete销毁或者malloc创建free销毁.动态内存的管理非常棘手,如果动态地创建了对象却没有显式得销毁,就会发生内存泄漏:如果在还有指针引用的时候释放了内存就会

第12章 动态内存

全局对象:启动时分配,结束时销毁 局部对象:程序块内分配,程序块外销毁 static对象:第一次使用分配,结束时销毁 动态内存使用new来分配对象,使用delete销毁对象 12.1两种智能指针 #include<memory> shared_ptr: 多个指针可以指向同一个对象 unique_ptr: 独占指向的对象 weak_ptr: 一个伴随类,指向shared_ptr管理的对象,是弱引用 智能指针也是模板,定义时需要指明类型 shared_ptr<string> p1; s

继承和动态内存分配

假设基类使用了动态内存分配,而且定义了析构函数.复制构造函数和赋值函数,但是在派生类中没有使用动态内存分配,那么在派生类中不需要显示定义析构函数.复制构造函数和赋值函数. 当基类和派生类采用动态内存分配时,派生类的析构函数.复制构造函数.赋值运算符都必须使用相应的基类方法来处理基类元素.这种要求是通过三种不同的方式来满足的.对于析构函数.这是自动完成的,也就是说在派生类的析构函数中无需显示调用基类的析构函数.对于构造函数,这是通过在初始化成员类别中调用基类的复制构造函数来完成的,如果不这样做,将