前言

在C++中,动态内存的管理是通过运算符newdelete来完成的。但使用动态内存很容易出现问题,因为确保在正确的时间释放内存是及其困难的。有时候我们会忘记内存的的释放,这种情况下就会产生内存泄露;有时候又会在尚有指针引用的情况下就用delete释放了内存,这样又会产生引用非法内存的指针(野指针)。因此,为了更容易地使用动态内存,C++标准库提供了两种智能指针,shared_ptrunique_ptr。shared_ptr允许多个指针指向同一个对象,unique_ptr则独占指向的对象。另外,还有一种叫weak_ptr的伴随类,他是一种弱引用,指向shared_ptr所管理的对象。三者定义于memory头文件中。

shared_ptr类

声明方式类似vector,属于模板类,如下

shared_ptr<string> p1;     //声明了一个指向string的智能指针,默认空

解引用等使用的方式类似普通指针

if( p1 && p1->empty())
*p1 = "hi!"; //如果p1指向一个空string,解引用并赋一个新值

两种智能指针公用的操作

shared_ptr<T> sp;
unique_ptr<T> up; //假设声明了两个名为p、q的智能指针
p->mem; //等价于(*p).mem
p.get(); //返回p中存放的指针 //交换二者保存的指针
swap(p,q);
p.swap(q);

shared_ptr独有的操作

p.unique();  //是否独有
p.use_count; //返回p共享对象的智能指针数量
p = q; //该操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放
make_shared<T>(args);//该方法返回一个shared_ptr,指向一个T类型的对象,并使用args初始化该对象,具体见下文 

make_shared函数

最安全的分配和使用动态内存的方法。

shared_ptr<int> p3 = make_shared<int>(42);//指向值为42的int的智能指针。
//或者也可以
auto p3 = make_shared<int>(42);

每一个shared_ptr都有关联的计数器,称为引用计数。当用一个shared_ptr ——p去初始化另个一个q时,或者将p作为参数传递给函数,或者作为函数返回值时,它关联的对象的引用计数就会递增;而如果给它赋一个新值或者是shared_ptr被销毁时,之前关联的计数器就减1,当一个shared_ptr的计数器为0时,他就会自动释放管理的对象的内存。

动态分配的const对象

const int *pci = new int();
const string *pcs = new string;
/*const修饰的对象必须初始化
虽然对象的值不能被修改,但是本身可以销毁的*/
delete pcs;//这是可以的

PS:用delete释放一个空指针总是没有错误的。

内存耗尽

如果内存耗尽的情况下,使用new会分配失败并抛出std::alloc异常。

可以使用

int *p2 = new (nothrow) int;

的形式来向new传递额外的参数,从而告诉它不能抛出异常,这种称为定位new。

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

Foo* factory(T arg)
{
return new Foo(arg);
} void use_factory(T arg)
{
Foo *p = factory(arg);
}

上述的代码,虽然p在离开作用域以后被销毁了,但他所指向的动态内存并没有被释放,不注意的话很可能内存泄漏!

所以,正确的做法如下:

void use_factory(T arg)
{
Foo *p = factory(arg);
//这块内存不再使用了就释放
delete p;
}

概括来说,由内置指针(不是智能指针)管理的动态内存在被显式地释放前会一直存在,直到手动释放或者程序结束时才会被回收。因此,智能指针的使用能够避免很多忘记释放内存等失误带来的麻烦。

另外,delete之后,虽然指针已经无效,但是它依然保存着释放的内存的地址,因此为了避免误操作最好将指针置空。

int *p(new int(42));
auto q = p;
delete p;
p = nullptr;

但是这样提供的保护还是有限的,如上述代码虽然将p置空,但是q仍然指向那块内存,仍然存在隐患。

 shared_ptr和new结合使用

//错误的方式,智能指针的构造函数由explicit修饰,不支持将内置指针隐式转换为智能指针
shared_ptr<int> p1= new int(1024);
//正确方式
shared_ptr<int> p2(new int(1024)); p2.reset(); //若p2是唯一指向,则释放其指向的内存并置空
p2.reset(q) //令p2指向q,否则置空 //同样的,返回值如果时内置指针也会报错
shared_ptr<int> clone(int p)
{
return new int(p); //错误,无法隐式转换为智能指针
}

  

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

void process(shared_ptr<int> ptr)
{ }
/*ptr离开作用域被销毁
-----------------
如果使用普通指针*/
int *x(new int(1024));
process(x);//出错,无法转换
process(shared_ptr<int>(x));//合法,但是x指向的内存在内部会被释放掉!!
int j = *x; //错误,未定义,x是一个空悬指针

上述代码中,shared_ptr通过x拷贝构造了一个智能指针ptr传递进process,这个时候的引用计数为1,而离开作用域后ptr被销毁,其指向的对象不再被引用,因此内存被回收,指针x因此无家可指变为野指针。

另外,也尽量避免使用get初始化另一个智能指针,也不要delete get()返回的内置指针。

使用自定义的释放操作

struct destination;       //连接目的地
struct connection; //连接信息
connection connect(destination *); //打开连接
void disconnect(connection); //关闭指定连接
void end_connection(connection *p)
{
disconnect(*p);
} void f(destination &d /*其他参数*/)
{
connection c = connect(&d);
shared_ptr<connection> p(&c,end_connection);
//使用连接
//当f退出时(即使为异常退出),connection也会被正确关闭
}

上述代码模拟的一个网络库的代码使用。

当p被销毁时,她不会对保存的的指针delete,而是调用end_connection,接下来end_connection会调用disconnect,从而确保连接被关闭。如果f正常退出,那么p的销毁会作为结束处理的一部分,如果发生了异常,p同样被销毁,连接从而被关闭。

unique_ptr类

顾名思义,独一无二的指针,与shared_ptr不同,某个时刻只能由一个unique_ptr指向一个给定对象。声明以及初始化如下

unique_ptr<int> p2(new int(42));

由于unique_ptr独享其对象,所以它不支持普通的拷贝和赋值操作

unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1); //错误:不支持拷贝
unique_ptr<string> p3'
p3 = p2; //错误,不支持赋值

unique_ptr的相关操作

unique_ptr<T> u1; //空unique_ptr指针
unique_ptr<T,D> u2; //使用D对象来代替delete释放
unique_ptr<T,D> u(new class()); u = nullptr; //释放u指向的对象并置空
u.release(); //u会放弃对该对象的控制权(内存不会释放),返回一个指向对象的指针,并置空自己
u.reset(); //释放u所指对象
u,reset(q);//如果提供了内置指针q,则指向q所指对象;否则u置空

当unique_ptr将要被销毁时,可以“特殊地”被拷贝或者赋值,比如下面这种情况

unique_ptr<int> clone(int p)
{
return unique_ptr<int>(new int(p)); //正确
} //或者
unique_ptr<int> clone(int p)
{
unique_ptr<int> ret(new int(p));
//......
return ret; //正确
}

weak_ptr类

weak_ptr是一种不控制所指向对像生命周期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数,当最后一个指向对象的shared_ptr被销毁时,对象会被释放(即使有weak_ptr指向)。

weak_ptr的操作

weak_ptr<T>w;
weak_ptr<T>w(sp);//使用一个shared_ptr初始化 w = p; //p可以是一个sp也可是一个wp。赋值后w,p共享对象 w.reset();//置空
w.use_count(); //同w共享对象的shared_ptr的数量
w.expired(); //w.use_count()为0返回true,否则返回false
w.lock(); //expired为true,返回一个空的shared_ptr;否则返回一个指向w的对象的shared_ptr

allocator类

定义在memory中,它提供了一种类型感知的内存分配方式,将内存分配和对象构造分离开来。它分配的内存是原始的、未构造的。基本用法如下:

allocator<string> alloc;  //分配string的allocator对象
auto const p = alloc,allocate(n); //分配n个未初始化的string

allocator的操作

allocator<T> a;
a.allocate(n); a.deallocate(p,n); /*释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象;p必须是
一个先前由allocate返回的指针,且n必须是p创建时所要求的大小。在调用deallocate之后,用户必须对
每个在这块内存中创建的对象调用destroy*/ a.construct(p,args);/*p必须是一个类型为T*的指针,指向一块原始内存;args被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象*/ a.destroy(p); //p为T*类型的指针,此算法对p所指向的对象执行析构函数

allocator分配的内存是未构造的,所以我们必须用construct构造对象,并且只能对构造了的对象执行destroy操作。

销毁的参考代码如下:

while(q != p)
alloc.destroy(--q);

 一旦所有元素被销毁后,就可以重新使用这部分内存来保存其他的string,也可以使用 alloc.deallocate(p,n)来释放内存。

参考资料

《C++ Primer 第5版》 电子工业出版社    作者:【美】  Stanley B. Lippman  && Josee Lajoie && Barbara E.Moo

C++相关:动态内存和智能指针的更多相关文章

  1. 【足迹C++primer】39、动态内存与智能指针(3)

    动态内存与智能指针(3) /** * 功能:动态内存与智能指针 * 时间:2014年7月8日15:33:58 * 作者:cutter_point */ #include<iostream> ...

  2. 12.动态内存和智能指针、 直接管理内存、shared_ptr和new结合使用

    12.动态内存和智能指针 1.智能指针分为两种shared_ptr和unique_ptr,后者独占所指向的对象.智能指针也是模板,使用时要用尖括号指明指向的类型.类似emplace成员,make_sh ...

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

    C++常见的内存分配方式有三种: 从静态存储区分配,这里主要是存储局部static对象,类的static成员以及定义在函数之外的变量: 从栈内存分配,这里主要是存储函数内的非static对象: 从堆内 ...

  4. c++学习笔记—动态内存与智能指针浅析

    我们的程序使用内存包含以下几种: 静态内存用来保存局部static对象.类static数据成员以及定义在任何函数之外的变量,在使用之前分配,在程序结束时销毁. 栈内存用来保存定义在函数内部的非stat ...

  5. 必须要注意的 C++ 动态内存资源管理(二)——指针对象简单实现

    必须要注意的 C++动态内存资源管理(二)——指针对象简单实现 四.拷贝类型的资源         上节我们说过,对于图片类型的资源我们有时候往往采用拷贝(如果对于那种公共图片,可能采用唯一副本,提供 ...

  6. c++ boost库学习二:内存管理->智能指针

    写过C++的人都知道申请和释放内存组合new/delete,但同时很多人也会在写程序的时候忘记释放内存导致内存泄漏.如下所示: int _tmain(int argc, _TCHAR* argv[]) ...

  7. boost的线程池和内存池 智能指针

    内存池为boost自带的 #include <boost/pool/pool.hpp> 或者另外一个开源的库: nedmalloc 一个高效率的库 线程池需要下载另外一个开源库 http: ...

  8. c++动态内存管理与智能指针

    目录 一.介绍 二.shared_ptr类 make_shared函数 shared_ptr的拷贝和引用 shared_ptr自动销毁所管理的对象- -shared_ptr还会自动释放相关联对象的内存 ...

  9. 【C++】C++中的动态内存解析

    目录结构: contents structure [-] 动态内存和智能指针 使用shared_ptr管理内存 使用new直接管理内存 shared_ptr和new结合使用 unique_ptr we ...

随机推荐

  1. OC语言(四)

    二十八.id类型(万能指针) 可以指向任何id对象(本身就是指针,不用*) id相当于NSObject *,类似于一种多态. 二十九.重写构造方法 new方法的实质:分配空间+alloc 和 初始化- ...

  2. LIRe 源代码分析 7:算法类[以颜色布局为例]

    ===================================================== LIRe源代码分析系列文章列表: LIRe 源代码分析 1:整体结构 LIRe 源代码分析 ...

  3. FNDCPASS Troubleshooting Guide For Login and Changing Applications Passwords

    In this Document   Goal   Solution   1. Error Starting Application Services After Changing APPS Pass ...

  4. LeetCode之“字符串”:最长回文子串

    题目要求: 给出一个字符串(假设长度最长为1000),求出它的最长回文子串,你可以假定只有一个满足条件的最长回文串.例如,给出字符串 "abcdzdcab",它的最长回文子串为 & ...

  5. LeetCode(62)-Two Sum

    题目: Given an array of integers, return indices of the two numbers such that they add up to a specifi ...

  6. EventQueue.invokeLater(new Runnable())

    public class EventQueueextends ObjectEventQueue 是一个与平台无关的类,它将来自于底层同位体类和受信任的应用程序类的事件列入队列. 它封装了异步事件指派机 ...

  7. RHEL 6 mdadm 实现Soft Raid

    环境:RHEL 6.9 x64 1.mdadm命令用于管理系统软件RAID硬盘阵列 格式为:"mdadm [模式] <RAID设备名称> [选项] [成员设备名称]". ...

  8. asp.net 下的中文分词检索工具 - jieba.net

    jieba是python下的一个检索库, 有人将这个库移植到了asp.net 平台下, 完全可以替代lucene.net以及盘古分词的搭配 之所以写这个, 其实是因为昨天面试时, 被问到网站的关键字检 ...

  9. Mysql创建索引

    1.索引作用 在索引列上,除了上面提到的有序查找之外,数据库利用各种各样的快速定位技术,能够大大提高查询效率.特别是当数据量非常大,查询涉及多个表时,使用索引往往能使查询速度加快成千上万倍. 例如,有 ...

  10. Pascal's Triangle(杨辉三角)

    Given numRows, generate the first numRows of Pascal's triangle. For example, given numRows = 5,Retur ...