前言

在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. 《java入门第一季》之面向对象(修饰符的概念和总结)

    还是由于eclipde等ide的强大功能,会自动提示你修饰符是否可行.所以对修饰符的作用了解即可: 修饰符:         权限修饰符:private,默认的,protected,public    ...

  2. 如何让你的传输更安全--基于SSL协议的通信模式

    之前发表在另一个平台的文章http://www.jointforce.com/jfperiodical/article/1218,现在发表到自己的博客上. 对于SSL/TLS协议,如果要每个开发者都自 ...

  3. ubuntu virtualbox xp无声音解决

    太简单了,记录一下解决方法,进入xp,打开设备管理器,对着ac97设备驱动 点右键,点更新驱动,更新一下就ok了. 这时候去控制面板,就可以看到有音频设备了. 具体步骤如下: 第一步,virtualb ...

  4. linux 网络不通问题排查

    基本的排错步骤(从上往下)ping 127.0.0.1ping的通说明tcp协议栈没有问题ping 主机地址 ping的通说明网卡没有问题ping 路由器默认网关 ping的通说明包可以到达路由器最后 ...

  5. "《算法导论》之‘线性表’":基于数组实现的单链表

    对于单链表,我们大多时候会用指针来实现(可参考基于指针实现的单链表).现在我们就来看看怎么用数组来实现单链表. 1. 定义单链表中结点的数据结构 typedef int ElementType; cl ...

  6. gtk程序运行报 main_loop!=NULL 错误的解决办法

    现象是将按钮的clicked Action与gtk_main_quit函数绑定起来会发生如上错误. 原因不明. 如果将window的destroy Action与gtk_main_quit绑定是没有问 ...

  7. Stripe Compaction

    借鉴于LevelDB.Cassandra的Compaction方法,https://issues.apache.org/jira/browse/HBASE-7667 提出了Stripe Compact ...

  8. Java 去掉字符串中的换行符回车符等

    去掉一个字符串中的换行符.回车符等,将连续多个空格替换成一个空格 String string = "this just a test" Pattern p = Pattern.co ...

  9. windows from 手风琴

    public class OutlookBar : Panel { private int SelectedBandHeight { get; set; } public int ButtonHeig ...

  10. 1-bit and 2-bit Characters

    We have two special characters. The first character can be represented by one bit 0. The second char ...