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. windows远程连接失败问题排查思路

    一般情况下,对WIN7的远程连接只需要5步即可完成远程连接的设置: 1).用户是否设置了密码 2).计算机属性-允许远程登录 3).设置计算机永不睡眠 4).关闭防火墙或者设置入站规则 5).排查Re ...

  2. oracle数据库之存储函数和过程

    一.引言     ORACLE 提供可以把 PL/SQL 程序存储在数据库中,并可以在任何地方来运行它.这样就叫存储过程或函数.过程和函数统称为 PL/SQL 子程序,他们是被命名的 PL/SQL 块 ...

  3. 第一周—Fortran语言学习

    使用教材:Fortran95程序设计[彭国伦] 第二章 编译器的使用 编译结果的好坏 1.翻译正确 2.执行文件的运行效率 3.翻译出来的执行码的长短 4.编译过程花费的时间 5.编译器提供Debug ...

  4. TCP系列06—连接管理—5、TCP fastopen(TFO)

    一.TFO背景 当前web和web-like应用中一般都是在三次握手后开始数据传输,相比于UDP,多了一个RTT的时延,即使当前很多应用使用长连接来处理这种情况,但是仍然由一定比例的短连接,这额外多出 ...

  5. PHP连接Redis操作函数

    phpredis是php的一个扩展,效率是相当高有链表排序功能,对创建内存级的模块业务关系 很有用;以下是redis官方提供的命令使用技巧: 下载地址如下: https://github.com/ow ...

  6. 【Linux】- 文件基本属性

    Linux系统是一种典型的多用户系统,不同的用户处于不同的地位,拥有不同的权限.为了保护系统的安全性,Linux系统对不同的用户访问同一文件(包括目录文件)的权限做了不同的规定. 在Linux中我们可 ...

  7. [Leetcode] 2.Add Two Numbers(List To Long,模拟)

    本题题意是指将两个数倒序存储在链表中,再将两数之和同样存储在链表中输出. 我最开始的思路是将每一位相加,再考虑是否进位,但这时就需要考虑一些情况,比较麻烦. 于是我决定采取另一种在网上新学到的方法:这 ...

  8. SocketServer-实现并发处理2

    转发自MnCu的博客: http://www.cnblogs.com/MnCu8261/p/5546823.html python基础之socket与socketserver ---引入 Socket ...

  9. [洛谷P3261][JLOI2015]城池攻占

    题目大意:有$n$个点的树,第$i$个节点有一个权值$h_i$,$m$个骑士,第$i$个骑士攻击力为$v_i$,一个骑士可以把从它开始的连续的父亲中比它小的节点攻破,攻破一个节点可以把攻击力加或乘一个 ...

  10. UVA.129 Krypton Factor (搜索+暴力)

    UVA.129 Krypton Factor (搜索+暴力) 题意分析 搜索的策略是:优先找长串,若长串不合法,则回溯,继续找到合法串,直到找到所求合法串的编号,输出即可. 注意的地方就是合法串的判断 ...