C++动态内存和智能指针 学习笔记.

个人学习笔记, 推荐看<C++ Primer>第五版第十二章动态内存.
为了赶时间, 只能粗略地了解了三个智能指针的用途…详细的后面有时间再补充.

C++内存管理

C++内存分配

c++中的程序加载到内存后按照代码区、数据区、堆区、栈区进行布局,其中数据区又可以分为自由存储区、全局/静态存储区和常量存储区,各区所长如下:

  • 栈区

函数执行的时候,局部变量的存储单元都在栈上创建,函数执行结束后存储单元会自动释放。栈内存分配运算内置于处理器指令集中,效率高,但分配内存容量有限。

  • 堆区

堆就是new出来的内存块,编译器不管释放,由应用程序控制,new对应delete。如果没释放掉,程序结束后,操作系统会自动回收。

来存储动态分配的对象----即那些在程序运行时分配的对象, 动态对象的生存期由程序来控制.

  • 自由存储区

C中malloc分配的内存块。用free结束生命周期。

  • 全局/静态存储区

全局变量静态变量被分配到同一块内存中,定义的时候就会初始化。

  • 常量存储区

比较特殊的存储区,存放常量,不允许修改。

堆和栈的区别

  • 管理方式

编译器自动管理,程序员控制

  • 空间大小

32位系统下,堆内存可以达到4GB,栈有一定的空间大小

  • 碎片管理

对于堆,频繁的new/delete肯定造成内存空间的不连续,产生大量内存碎片降低程序效率;栈由于遵循先进后出的规则,不会产生空隙

  • 生长方向

堆是向上生长的,即向着内存地址增加的方向增长;而栈是向着内存地址减小的方向增长的

  • 分配方式

堆是动态分配的栈有动态分配和静态分配之分:静态分配由编译器完成,动态分配由alloca函数完成,即使是动态分配,依然是编译器自动释放

  • 分配效率

计算机底层提供了栈的支持,分配了专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这决定了栈的效率会比较高。堆则是由C/C++函数库提供的,机制比较复杂,比如为了分配某个大小的内存需要在堆内存中搜索可用足够大小的空间,效率比栈要低的多

参考资料: 如何编写高效、优雅、可信代码系列(2)——你真的会用new吗

动态内存与智能指针

动态内存的管理是通过一对运算符完成的:

  • new 在动态内存中为对象分配空间并返回一个指向该对象的指针
  • delete 接受一个动态对象的指针, 销毁该对象, 并释放与之关联的内存

动态内存的使用很容易产生内存泄漏和引用非法内存指针的问题, 所以为了更安全更容易地使用动态内存, 新的标准库提供了两种智能指针类型来管理动态对象.

智能指针的行为类似常规指针, 重要的区别是它负责自动释放所指向的对象.

新标准库里提供的这两种智能指针的区别在于管理底层指针的方式:

  • shared_ptr 允许多个指针指向同一对象

  • unique_ptr "独占"所指向的对象

标准库还定义了一个名为weak_ptr的伴随类, 它是一种弱引用, 指向shared_ptr所管理的对象.

以上三种类型都在memory头文件中.

智能指针shared_ptr

首先必须确定一件事, 智能指针也是类模板, 因此当创建一个智能指针时, 必须给出指针可以指向的类型, 定义如下:

shared_ptr<string> p1;      //可以指向string
shared_ptr<list<int>> p2;   //可以指向int的list

默认初始化的智能指针中都保存着一个空指针.

智能指针的使用方式与普通指针类似.

make_shared 函数

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数, 此函数在动态内存中分配一个对象并初始化它, 返回指向此对象的shared_ptr.

使用

shared_ptr<int> p3 = make_shared<int>(42);
// 指向一个值为42的int的shared_ptr
shared_ptr<int> p3 = make_shared<int>();
// 指向一个值初始化的int 的shared_ptr, 值为0

通常使用

auto p6 = make_shared<int>();

来定义一个对象保存make_shared的结果.

shared_ptr的拷贝和赋值

当进行拷贝或复制操作时, 每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象 .

auto p = make_shared<int>();    // p指向的对象只有p一个引用者
auto q(p);                      // p和q指向相同对象, 此对象有两个引用者

可以这么认为, shared_ptr有一个关联的计数器, 通常称其为引用计数.

无论何时我们拷贝一个shared_ptr, 计数器都会递增.

当我们给shared_ptr赋予一个新值或是shared_ptr被销毁(例如局部离开了作用域), 计数器都会递减.

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

auto r = make_shared<int>();    // r指向的对象只有r一个引用者
r = q;                          // 给r赋一个新的值, 此时r已经指向另外一个地址
                                // 递增q指向对象的引用计数
                                // 递减r原来所指向对象的引用计数
                                // 此时r原来所指向对象已经没有饮用者, 即引用计数为0, 自动释放.

shared_ptr的销毁与释放

当指向一个对象的最后一个shared_ptr被销毁时, shared_ptr类会自动通过析构函数销毁此对象.

关于shared_ptr类的析构函数 : shared_ptr的析构函数会递减它所指向的对象的引用计数. 如果引用计数变为0, shared_ptr的析构函数就会销毁对象并释放它占用的内存.

如果你将shared_ptr存放于一个容器中,之后不再需要全部元素,只使用七张一部分,要记得用erase删除不再需要的那些元素.

以上讨论基于我们使用的类分配的资源都与对应对象的生存期一致…没时间了…先不讨论其他情况了, 后面再补充.

智能指针unique_ptr

一个unique_ptr"拥有"它所指向的对象, 换句话说某个时刻只能有一个unique_ptr指向一个给定对象. 当unique_ptr被销毁时, 它所指向的对象也会被销毁.

unique_ptr的定义和所有权转移

与shared_ptr不同的是,没有make_shared函数. 当我们需要定义unique_ptr时,需要绑定到一个new返回的指针上, 另初始化unique_ptr必须采用直接初始化的形式:

unique_ptr<double> p1;  // 可以指向一个double的unique_ptr
unique_ptr<int> p2(new int(0)); //p2指向一个值为0的int

由于一个unique_ptr拥有它所指向的对象, 因此unique_ptr不支持普通的拷贝或赋值.

但是可以通过调用releasereset将指针的所有权从一个(非const)的unique_ptr转移给另一个unique:

// 将所有权从p1转移给p2
unique_ptr<double> p2(p1.release());  // release 将p1置空
unique_ptr<int> p3(new int(4)); 
// 将所有权从p3转移给p2
p2.reset(p3.release());                 // reset释放了p2原来指向的内存

release成员返回unique_ptr当前保存的指针并将指针置空. 因此, p2被初始化为p1原来保存的指针, p1指针自身被置空. 注意: release并不会释放p1原指针指向的内存, 所以记得delete(p2)

reset成员接受一个可选的指针参数, 令unique_ptr重新指向给定的指针. 如果unique_ptr不为空, 则其原来指向的对象会被释放.

unique_ptr的传参和返回

不能拷贝unique_ptr的规则有一个例外: 可以拷贝或赋值一个将要被销毁的unique_ptr.

例如:

// 从函数返回

unique_ptr<int> clone(int p)
{
    // 正确 并且返回一个指向p的指针
    return unique_ptr<int>(new int(p));
}

// 返回局部对象的拷贝

unique_ptr<int> clone(int p)
{
    unique_ptr<int> ret(new int(p));
    return ret;
}

编译器知道将要返回的对象即将被销毁, 在此情况下可以执行一种特殊的"拷贝".
(编译器真NB!!!)

智能指针weak_ptr

weak_ptr是一种不控制所指向对象生存期的智能指针.

它指向的是由shared_ptr管理的对象, 它绑定到一个shared_ptr时并不会增加引用计数. 但是当shared_ptr的引用计数为0时, 即使还有weak_ptr指向对象, 对象也依旧会被释放.

创建一个weak_ptr:

auto p = make_shared<int>(13);
weak_ptr<int> wp(p);            //wp弱共享p

由于wp指向的对象可能不存在, 所以不能使用weak_ptr直接访问对象, 而必须要调用lock.

此函数检查weak_ptr指向的对象是否存在. 如果存在, lock会返回一个指向共享对象的shared_ptr

if(shard_ptr<int> np = wp.lock()) //如果np不为空条件成立, 即wp指向的对象还存在时
{
    // 在if中np可与p共享
}
//出了作用域以后 np被释放

为了赶时间, 只能粗略地了解了三个智能指针的用途…详细的后面有时间再补充.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值