1、shared_ptr共享智能指针

  std::shared_ptr使用引用计数,每个shared_ptr的拷贝都指向相同的内存,在最后一个shared_ptr析构的时候,内存才会释放。

1.1 基本用法

1.1.1 初始化

  shared_ptr可以通过make_shared来初始化,也可以通过shared_ptr<T>辅助函数和reset方法来初始化。智能指针的用法和普通指针的用法类似,不过不需要自己管理分配的内存,对于没有初始化的指针,只能通过reset来初始化,当智能指针有值,reset会使计数器减1。智能指针可以通过重载的bool来判断是否为空。

#include <iostream>
#include <memory> using namespace std; int main()
{
//智能指针初始化
shared_ptr<int> p = make_shared<int>();
shared_ptr<int> p(new int());
shared_ptr<int> p1 = p;
shared_ptr<int> ptr; //所指的对象会被重置,不带参数则是销毁
ptr.reset(new int()); if(ptr)
{
cout << "ptr is not null" << endl;
} return ;
}

  智能指针不能通过原始指针来初始化:

shared_ptr<int> p = new int(); //编译报错,不能直接赋值

1.1.2 获取原始指针

  当需要获取原始指针的时候,可以通过get来返回原始指针。不能释放,如果释放会出错。

shared_ptr<int> ptr(new int());
int* p = ptr.get();
delete p; //error

1.1.3 指定删除器

  智能指针支持指定删除器,在指针引用为0的时候自动调用。支持普通函数和lambda表达式。

//普通函数
void DeleteIntPtr(int *p) {delete p;}
shared_ptr<int> p(new int(), DeleteIntPtr);
//lambda表达式
shared_ptr<int> p(new int(), [](int *p) {delete p;});

  当智能指针管理动态数组的时候,默认的删除器不支持数组对象。需要指定删除器,自定义删除器或者使用改善的默认修改器都可以。

shared_ptr<int> p(new int[], [](int *p) {delete[] p;}); //lambda
shared_ptr<int> p1(new int[], default_delete<int []>); //指定delete []

1.2 注意问题

  a.避免一个原始指针初始化多个shared_ptr。

int* p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);

  b.不要在参数实参中创建shared_ptr。

func(shared_ptr<int>(new int), g());

  不同的编译器可能有不同的调用约定,如果先new int,然后调用g(),在g()过程中发生异常,但是shared_ptr没有创建,那么int的内存就会泄漏,正确的写法应该是先创建智能指针。

shared_ptr<int> p(new int);
f(p, g());

  c.避免循环使用,循环使用可能导致内存泄漏

#include <iostream>
#include <memory> using namespace std; struct A;
struct B; struct A
{
shared_ptr<B> bptr;
~A() { cout << "A is deleted." << endl; }
}; struct B
{
shared_ptr<A> aptr;
~B() { cout << "B is deleted." << endl; }
}; int main()
{
shared_ptr<A> ap(new A);
shared_ptr<B> bp(new B); ap->bptr = bp;
bp->aptr = ap; return ;
}

  这个最经典的循环引用的场景,结果是两个指针A和B都不会删除,存在内存泄漏。循环引用导致ap和bp的引用计数为2,离开作用域之后,ap和bp的引用计数为1,并不会减0,导致两个指针都不会析构而产生内存泄漏。

  d.通过shared_from_this()返回this指针。不要将this指针作为shared_ptr返回出来,因为this指针本质是一个裸指针,这样可能导致重复析构。

#include <iostream>
#include <memory> using namespace std; struct A
{
shared_ptr<A> GetSelf()
{
return shared_ptr<A>(this);
} ~A() { cout << "A is deleted." << endl; }
}; int main()
{
shared_ptr<A> ap(new A);
shared_ptr<A> ap2 = ap->GetSelf(); return ;
} //执行结果
A is deleted.
A is deleted.

  这个例子中,由于同一指针(this)构造了两个只能指针ap和ap2,而他们之间是没有任何关系的,在离开作用域之后this将会被构造的两个智能指针各自析构,导致重复析构的错误。当然,也有解决办法,解决办法在之后的weak_ptr介绍。

2、unique_ptr独占智能指针

2.1 初始化

  unique_ptr是一个独占型智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。只能通过函数来返回给其它的unique_ptr,比如move函数,但是转移之后,不再对之前的指针具有所有权。

unique_ptr<int> uptr(new int());
unique_ptr<int> uptr2 = uptr; //error
unique_ptr<int> uptr3 = move(uptr); //uptr将变为null

2.2 特点

2.2.1 数组

  unique_ptr和shared_ptr相比除了独占之外,unique_ptr还可以指向一个数组。

unique_ptr<int []> ptr(new int[]);   //ok
ptrp[] = ; shared_ptr<int []> ptr2(new int[]); //error

2.2.2 删除器

  unique_ptr必须指定删除器类型,不像shared_ptr那样直接指定删除器。

shared_ptr<int> ptr(new int(), [](int *p){delete p;});           //ok
unique_ptr<int> ptr2(new int(), [](int *p){delete p;});           //error
unique_ptr<int, void(*)(int *)> ptr2(new int(), [](int *p){delete p;}); //ok

  通过指定函数类型,然后通过lambda表达式实现是可以,但是如果捕获了变量将会编译报错,因为lambda表达式在没有捕获变量的情况下可以直接转换为函数指针,但是捕获了变量就无法转换。如果要支持,可以通过std::function来解决。

unique_ptr<int, void(*)(int *)> ptr2(new int(), [&](int *p){delete p;});            //error
unique_ptr<int, std::function<void(int*)>> ptr2(new int(), [&](int *p){delete p;}); //ok

  unique_ptr支持自定义删除器。

#include <iostream>
#include <memory>
#include <functional> using namespace std; struct DeleteUPtr
{
void operator()(int* p)
{
cout << "delete" << endl;
delete p;
}
}; int main()
{
unique_ptr<int, DeleteUPtr> p(new int()); return ;
}

3、weak_ptr弱引用智能指针

弱引用智能指针weak_ptr用来监视shared_ptr,不会使引用技术加1,也不管理shared_ptr内部的指针,主要是监视shared_ptr的生命周期。weak_ptr不共享指针,不能操作资源,它的构造和析构都不会改变引用计数。

3.1 基本用法

3.1.1 观测计数

  通过use_count()方法来获得当前资源的引用计数。

shared_ptr<int> sp(new int());
weak_ptr<int> wp(sp); cout << wp.use_count() << endl; //输出1

3.1.2 观察是否有效

shared_ptr<int> sp(new int());
weak_ptr<int> wp(sp); if(wp.expired())
{
cout << "sp 已经释放,无效" << endl;
}
else
{
cout << "sp 有效" << endl;
}

3.1.3 监视

  可以通过lock方法来获取所监视的shared_ptr。

#include <iostream>
#include <memory> using namespace std; weak_ptr<int> gw; void f()
{
//监听是否释放
if(gw.expired())
{
cout << "gw is expired." << endl;
}
else
{
auto spt = gw.lock();
cout << *spt << endl;
}
} int main()
{
{
auto p = make_shared<int>();
gw = p;
f();
}
f(); return ;
} //执行结果 gw is expired.

3.2 返回this指针

  sharerd_ptr不能直接返回this指针,需要通过派生std::enable_shared_from_this类,并通过其方法shared_from_this来返回智能指针,因为std::enable_shared_from_this类中有一个weak_ptr,这个weak_ptr用来观测this指针,调用shared_from_this方法时,调用了内部的weak_ptr的lock()方法,将所观测的sharerd_ptr返回。

#include <iostream>
#include <memory> using namespace std; struct A:public enable_shared_from_this<A>
{
shared_ptr<A> GetSelf()
{
return shared_from_this();
} ~A()
{
cout << "A is deleted." << endl;
}
}; int main()
{
shared_ptr<A> spy(new A);
shared_ptr<A> p = spy->GetSelf(); //ok return ;
} //执行结果
A is deleted.

  在外面创建A对象的智能指针通过该对象返回this的智能指针是安全的,因为shared_from_this()是内部weak_ptr调用lock()方法之后返回的智能指针,在离开作用域之后,spy的引用计数为0,A对象会被析构,不会出现A对象被析构两次的问题。

  需要注意的是,获取自身智能指针的函数仅在share_ptr<T>的构造函数调用之后才能使用,因为enable_shared_from_this内部的weak_ptr只有通过shared_ptr才能构造。

3.3 解决循环引用问题

  shared_ptr的循环引用可能导致内存泄漏,之前的例子不再赘述,通过weak_ptr可以解决这个问题,怎么解决呢?答案是,将A或者B任意一个成员变量改为weak_ptr即可。

#include <iostream>
#include <memory> using namespace std; struct A;
struct B; struct A
{
shared_ptr<B> bptr;
~A() { cout << "A is deleted." << endl; }
}; struct B
{
weak_ptr<A> aptr;
~B() { cout << "B is deleted." << endl; }
}; int main()
{
shared_ptr<A> ap(new A);
shared_ptr<B> bp(new B); ap->bptr = bp;
bp->aptr = ap; return ;
} //执行结果
A is deleted.
B is deleted.

  这样在对B成员赋值时,即bp->aptr = ap,由于aptr是weak_ptr,并不会增加引用计数,所以ap的计数仍然是1,在离开作用域之后,ap的引用计数会减为0,A指针会被析构,析构之后,其内部的bptr引用计数会减1,然后离开作用域之后,bp引用计数从1减为0,B对象也被析构,所以不会发生内存泄漏。

C11内存管理之道:智能指针的更多相关文章

  1. Android内存管理之道

    相信一步步走过来的Android从业者,每个人都会遇到OOM的情况.如何避免和防范OOM的出现,对于每一个程序员来说确实是一门必不可少的能力.今天我们就谈谈在Android平台下内存的管理之道,开始今 ...

  2. 以对象管理资源——C++智能指针auto_ptr简介

    auto_ptr是C++标准库提供的类模板,它可以帮助程序员自动管理用new表达式动态分配的单个对象.auto_ptr对象被初始化为指向由new表达式创建的对象,当auto_ptr对象的生命期结束时, ...

  3. 你说你会C++? —— 智能指针

    智能指针的设计初衷是:      C++中没有提供自己主动回收内存的机制,每次new对象之后都须要手动delete.稍不注意就memory leak. 智能指针能够解决上面遇到的问题. C++中常见的 ...

  4. c++内存泄漏原因及解决办法(智能指针)

    内存泄漏 由于疏忽或错误造成程序未能释放已经不再使用的内存的情况.内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费. 内存泄露的 ...

  5. C++ 引用计数技术及智能指针的简单实现

    一直以来都对智能指针一知半解,看C++Primer中也讲的不够清晰明白(大概是我功力不够吧).最近花了点时间认真看了智能指针,特地来写这篇文章. 1.智能指针是什么 简单来说,智能指针是一个类,它对普 ...

  6. C++智能指针详解

    本文出自http://mxdxm.iteye.com/ 一.简介 由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete.程序员忘记 delete,流程太复杂,最 ...

  7. 使用智能指针来管理对象 (基于RAII)

    ////一个简单的防止内存泄露的例子//void test() { //使用RAII的特性管理资源 //当智能指针unique_ptr被销毁时,它指向的对象也将被销毁 //这里test函数返回后 p将 ...

  8. C++中智能指针的设计和使用

    转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7561235 智能指针(smart pointer)是存储指向动态分配(堆 ...

  9. 【转】C++ 智能指针详解

    一.简介 由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete.程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 ...

随机推荐

  1. VBA基础之Excel 工作表(Sheet)的操作(二)

    二. Excel 工作表(Sheet)的操作1. Excel 添加工作表(Sheet) 方法名 参数 参数值 说明 Add Before 工作表名称 在指定的工作表前面插入新的工作表 After 工作 ...

  2. html5 canvas绘制环形进度条,环形渐变色仪表图

    html5 canvas绘制环形进度条,环形渐变色仪表图                                             在绘制圆环前,我们需要知道canvas arc() 方 ...

  3. HTML5文档的各个组成部分分类

    <!DOCTYPE html><!--声明文档结构类型--> <html lang="zh-cn"><!--声明文档文字区域--> ...

  4. 20145214 《Java程序设计》第7周学习总结

    20145214 <Java程序设计>第7周学习总结 教材学习内容总结 时间的度量 格林威治标准时间(GMT),现已不作为标准时间使用,即使标注为GMT(格林威治时间),实际上谈到的的是U ...

  5. jspSmartUpload上传下载使用例子

    --------------------------------------------------------------------- ServletUpload.java 上传 package ...

  6. LintCode-374.螺旋矩阵

    螺旋矩阵 给定一个包含 m x n 个要素的矩阵,(m 行, n 列),按照螺旋顺序,返回该矩阵中的所有要素. 样例 给定如下矩阵: [     [ 1, 2, 3 ],     [ 4, 5, 6 ...

  7. error LNK2019: 无法解析的外部符号 该符号在函数 中被引用 解决方案

    需要添加lib或者dll库.项目-属性-配置属性-链接器-输入-附件依赖项,添加需要的lib. 例如我在运行OSG程序的时候,忘记添加了附件依赖项就会报这个错. 解决方案如图.

  8. 【week3】四则运算 单元测试

    上一周的四则运算有bug,这次补充正确代码: // 中缀转后缀 public String[] SolveOrder(String[] in, HashMap<String, Integer&g ...

  9. zookeeper伪集群安装

    记录下zookeeper伪分布式搭建的过程,假设系统已经配置好了JAVA环境. 1.准备环境 linux服务器一台,下载某个版本的zookeeper压缩包,下载链接:http://apache.cla ...

  10. 【Todo】【转载】JVM学习

    先参考如下这个系列<聊聊JVM> http://blog.csdn.net/column/details/talk-about-jvm.html