前言

在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. 【Qt编程】设计ColorBar颜色栏

    画过图的都知道,我们常常用颜色的深浅来表示值的大小,在Matlab作图中,我们使用的是colorbar这个函数来给出颜色的直观参考.下面给出Matlab的示例:在Matlab命令窗口输入: figur ...

  2. android使用ViewPager实现欢迎引导页

    android使用ViewPager实现欢迎引导页 大多数APP第一次启动的时候,都会有一个引导界面,左右滑动,到最后一张,用户点击才再次进入主界面.当第二次启动的时候,则直接进入主界面. 这种效果一 ...

  3. SQL-Teradata基础

    1.创建一个和表 pnr_1 结构一样的表 Create table pnr_2 as pnr_1 with no data  不含数据 Create table pnr_2 as pnr_1 wit ...

  4. windows linux—unix 跨平台通信集成控制系统

    首先,我们可以用到这个开源的开发包: mdk(Micro-Development-Kit)微量级软件开发包,提供几个常用类,主要实现了一个高性能的并发服务器引擎 使用c++开发,是一个跨平台的开发包, ...

  5. BP 神经网络

    BP(Back Propagation)网络是1986年由Rumelhart和McCelland为首的科学家小组提出,是一种按误差逆传播算法训练的多层前馈网络,是目前应用最广泛的神经网络模型之一.BP ...

  6. 【编程练习】kmp算法代码

    代码来自: http://blog.csdn.net/v_JULY_v #include "StdAfx.h" #include <iostream> using na ...

  7. How to download the installation package by ZOL Downer

    How to download the installation package by ZOL Downer Ma Genfeng (Guangdong Unitoll Services incorp ...

  8. Mac OS 终端常用命令基础

    基础概念 OS X 采用的Unix文件系统,所有文件都挂在跟目录" /" 下面,所以不在要有Windows 下的盘符概念.比如什么"C:"你在桌面上看到的硬盘都 ...

  9. iframe不起作用?你可能碰到它了。

    有一个需求要在iframe里显示一个网站,但设置iframe的src后,iframe并没有起作用.然后打开控制台,发现错误如下: , 对其搜索找到了答案:https://stackoverflow.c ...

  10. Course3-Python文件I/O

    1. 读取键盘输入 Python提供了两个内置函数从标准输入读入一行文本,默认的标准输入是键盘.如下: 1). raw_input. raw_input([prompt]) 函数从标准输入读取一个行, ...