shared_ptr和new结合使用

一个shared_ptr默认初始化为一个空指针。我们也可以使用new返回的指针来初始化一个shared_ptr:
shared_ptr<double> p1;
shared_ptr<int> p2(new int(42)); // p2指向一个值为42的int

接受指针参数的智能指针构造函数是explicit的,因此,我们不能将一个内置指针隐式的转换为一个智能指针,必须使用直接初始化形式:

shared_ptr<int> p1 = new int(1024); // 错误,必须使用直接初始化形式
shared_ptr<int> p2(new int(1024));

p1的初始化隐式的要求编译器用一个new返回的int* 来初始化一个shared_ptr。由于我们不能进行内置指针到智能指针间的隐式转换,因此这条初始化语句是错误的。同样的,一个返回值为shared_ptr的函数不能在其返回语句中转换一个普通指针:

shared_ptr<int> clone(int p) {

    return new int(p); // 错误,隐式转换为shared_ptr
}

我们必须将shared_ptr显示绑定到一个想要返回的指针上:

shared_ptr<int> clone(int p) {

    return shared_ptr<int>(new int(p));
}

默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete来释放它所关联的对象。 我们也可以把智能指针绑定到其他类型的指针上,但是我们必须提供自己的删除操作来替代delete:
定义和改变shared_ptr的其他方法
shared_ptr<T> p(q) p管理内置指针q所指向的对象,q必须指向new分配的内存,且能转换为T* 类型
shared_ptr<T> p(u) p从unique_ptr  u那里接管了对象的所有权,将u置为空
shared_ptr<T> p(q, d) p接管了内置指针q所指向的对象的所有权,q必须能转换为T*类型,p将使用可调用对象d来代替delete
shared_ptr<T> p(p2, d) p是shared_ptr  p2的拷贝,唯一的区别是p将用可调用对象d来代替delete
p.reset() 若p是唯一指向其对象的shared_ptr,reset操作会释放此对象。
p.reset(q) 若传递了参数q,会令p指向q,否则将p置为空
p.reset(q. d) 若还传递了参数d,则使用可调用对象d来代替delete

  • 不要混合使用普通指针和智能指针
shared_ptr可以协调对象的析构,但这仅限于自身的拷贝,因此,我们尽量用make_shared而不要用new的原因就在这。这样,我们在分配对象的同时将shared_ptr与之绑定,从而避免了将同一块内存绑定到多个独立创建的shared_ptr上。
void process(shared_ptr<int> ptr) {
// 使用ptr
} // ptr离开作用域,被销毁

process的参数是值传递形式的,因此在调用process函数时,会拷贝shared_ptr,拷贝一个shared_ptr会增加引用计数,假设原来的shared_ptr只有自身一个引用者,在调用process函数时,其引用技术变为2,在process结束后,ptr被释放,引用计数变为1。因此,当局部变量ptr被销毁时,ptr指向的内存并不会被释放。

我们在调用此函数的正确方法时传递给它一个shared_ptr:
shared_ptr<int> p(new int(42)); // 引用计数为1
process(p); //拷贝p,在函数结束前引用计数为2,函数执行结束后,引用计数变为1 int i = *p; // i == 42

我们前面说到,不能将一个内置指针隐式转换为一个shared_ptr,因此,我们不能将一个内置指针直接传递给process函数,但可以传递给它一个临时的shared_ptr,这个shared_ptr使用一个内置指针显示构造的:

int* x(new int(1024));
process(x); // 错误,不能将一个int*转换为一个shared_ptr,因位构造函数时explicit的
process(shared_ptr<int>(x)); // 合法的,但x所管理的内存会被释放 int j = *x; // 错误,x是一个空悬指针!

在这段程序中shared_ptr是临时构造的,因此在这个shared_ptr只有ptr一个引用者,在函数结束后,ptr的析构函数被执行,它所管理的内存被释放,因为和x所管理的内存相同,因此x将成为一个空悬指针,解引用x将会发生错误,这样的行为是未定义的。
  • 不要用get成员函数初始化另一个智能指针或为只能指针赋值
智能指针类定义了一个get的成员函数,该成员函数返回一个内置指针,指向智能指针管理的对象。此函数为了向不使用智能指针的代码传递一个普通指针,使用get返回的指针的代码不能delete此指针。
将一个智能指针绑定到get返回的指针上时错误的:
shared_ptr<int> p(new int(1024));
int* q = p.get(); { // 新的作用域 shared_ptr<int> p1(p); } // 作用域结束,p1被销毁,p1所管理的对象被释放 int foo = *p; // 未定义,p指向的内存已经被释放了

● get将指针的访问权限传递给代码,只有在确定代码不会delete指针的情况下,才能使用get。特别的,永远用get的返回值初始化一个智能指针或给一个智能指针赋值。

  • 其他的shared_ptr操作
 reset操作: 
p = new int(1024); // p是一个shared_ptr类型, 发生错误,不能将一个指针赋予shared_ptr
p.reset(new int(1024)); // p指向一个新对象

unique操作: 如果一个shared_ptr对象唯一指向它所管理的内存对象,则返回true。

reset会更新引用计数,如果需要的话,会释放p指向的对象。reset经常unique一起使用,来控制多个shared_ptr共享的对象。
if (! p.unique()) //如果p补是它所管理对象的唯一引用者,则为它重新分配
p.reset(new int(100));
*p += newVal; // 现在p是唯一的用户,可以改变对象的值

智能指针和异常

使用异常处理的程序能在异常发生后令程序继续,这种程序需要确保异常发生后资源能被正确的释放,简单的方法,我们可以使用智能指针。
如果使用智能指针,即使程序块过早结束,智能指针类也能正确释放资源:
void f() {
shared_ptr<int> sp(new int(42));
// 抛出异常,未在f中捕获
// 函数结束后shared_ptr正确释放资源
}

上面的代码中,无论是正常结束或发生异常,shared_ptr都会释放资源。

相对的,我们直接管理的内存是不会自动释放的:
void f() {
int* ip = new int(42);
// 发生异常,且在f中未捕获
delete ip;
}

在上面的代码中,如果在资源被释放前发生异常,且未捕获,那么new所分配的内存资源就不会被释放。

  • 一个智能指针的例子,使用我们自己的删除器
例如有一个C和C++都能使用的简单的网络库:
struct destination; // 表示我们正在连接什么
struct connection; // 使用连接所需的信息
connection connect(destination*); // 打开连接
void disconnect(connection); // 关闭给定的连接
void f (destination& d) {
// 获得一个连接,记住使用完要关闭它
connection c = connect(d);
// 使用连接
// 如果在f结束之前忘记调用disconnect,就无法关闭c了
}

如果connection有一个析构函数,就可以在f结束之后关闭c,但是connection并没有析构函数,我们可以使用shared_ptr来管理这些对象。默认情况下,shared_ptr使用delete进行释放内存,为了使用shared_ptr来管理connection,我们需要提供自己的删除器:

void end_connection(connection* p) { disconnect(*p); }

当创建一个connection的shared_ptr时,可以传递一个指向删除器函数的参数:

void f (destination& d) {

    connection c = connect(&d);
shared_ptrM<connection> p(&c, end_connection);
// 使用连接
// f结束后,connection被正确关闭
}

p被销毁时,它不会对自己保存的指针使用delete,而是调用end_connection。

  • 智能指针的陷阱:
●   不使用相同的内置指针值初始化(或reset)多个智能指针
●   不delete  get()返回的指针
●   不使用get() 初始化或reset另一个智能指针
●   如果使用get() 返回的指针,当最后一个对应的智能指针销毁后,该指针就变为无效了
●   如果智能指针管理的不是new分配的内存,记得传递给它一个删除器

C++ Primer : 第十二章 : 动态内存之shared_ptr与new的结合使用、智能指针异常的更多相关文章

  1. C++ Primer : 第十二章 : 动态内存之shared_ptr类实例:StrBlob类

    StrBlob是一个管理string的类,借助标准库容器vector,以及动态内存管理类shared_ptr,我们将vector保存在动态内存里,这样就能在多个对象之间共享内存. 定义StrBlob类 ...

  2. C++ Primer : 第十二章 : 动态内存之shared_ptr类

    在C++中,动态内存是的管理是通过一对运算符来完成的:new  ,在动态内存中为对象分配空间并返回一个指向该对象的指针,delete接受一个动态对象的指针,销毁该对象,并释放该对象关联的内存. 动态内 ...

  3. C++ Primer : 第十二章 : 动态内存之动态数组

    动态数组的分配和释放 new和数组 C++语言和标准库提供了一次分配一个对象数组的方法,定义了另一种new表达式语法.我们需要在类型名后跟一对方括号,在其中指明要分配的对象的数目. int* arr ...

  4. C++ Primer : 第十二章 : 动态内存之动态内存管理(new和delete)

    C++语言定义了两个运算符来分配和释放动态内存:运算符new分配内存,运算符delete释放new分配的内存. 运算符new和delete 使用new动态分配和初始化对象 在自由空间分配的内存是无名的 ...

  5. C++ Primer : 第十二章 : 动态内存之unique_ptr和weak_ptr

    unique_ptr 一个unique_ptr拥有它所管理的对象,与shared_ptr不同,unique_ptr指向的对象只能有一个用户.当unique_ptr被销毁后,它所指向的对象也被销毁. 定 ...

  6. C++ Primer : 第十二章 : 动态内存之allocator类

    标准库allocator类定义在头文件 <memory>中.它帮助我们将内存分配和构造分离开来,它分配的内存是原始的.未构造的. 类似vector,allocator也是一个模板类,我们在 ...

  7. C++ Primer 5th 第12章 动态内存

    练习12.1:在此代码的结尾,b1 和 b2 各包含多少个元素? StrBlob b1; { StrBlob b2 = {"a", "an", "th ...

  8. Linux内核设计与实现 总结笔记(第十二章)内存管理

    内核里的内存分配不像其他地方分配内存那么容易,内核的内存分配不能简单便捷的使用,分配机制也不能太复杂. 一.页 内核把页作为内存管理的基本单位,尽管处理器最小寻址坑是是字或者字节.但是内存管理单元MM ...

  9. C++Primer 第十二章

    //1.标准库提供了两种智能指针类型来管理动态对象,均定义在头文件memory中,声明在std命名空间. // shared_ptr:允许多个指针指向同一个对象. // unique_ptr:独占所指 ...

随机推荐

  1. K2 BPM + SAP,实现全方面管理企业

    K2作为专业的BPM.工作流管理平台供应商,面向庞大的SAP用户群体,除了提供产品化的SAP集成工具「K2 connect」产品之外,更拥有一套得到众多客户验证的集成解决方案. 此方案可供SAP用户或 ...

  2. Linux Lab and project latest

    samba : start your samba service netlogon syslog vi /usr/local/samba vi /usr/samba/etc/smb.conf smbc ...

  3. 【转】Centos系统文件与用户权限分配详解ftp,nginx,php

    linux系统中权限是非常完善的一个功能了,我们如果设置不正确文件就无法使用了,像我们以一般情况需要把文件权限设置为777或644了,对于用户权 限就更加了,像素ftp,nginx,php这些我们都可 ...

  4. JAVA工作方式

    public class Hellow { int eyes = 2; int ears = 2; int legs = 4; void run(){ //方法 System.out.println( ...

  5. MVC5 烂笔头

    HttpContent Controller:HttpContextBase View:HttpContext.Current View的搜寻顺序:本文件夹.本共享.根共享等 class=" ...

  6. 更改layout的布局

    创建了一个layout,缺省都是LinearLayout,原来都在xml把他改成其他布局,习惯了拖放的方式觉得挺麻烦. 其实vs2013有这个功能,就是在设计界面单机右键可以删除布局,然后拖一个布局进 ...

  7. 一篇文章教你学会基础的HTML

    html是学习做网页的基础,漂亮的网页与布局就是由有些html代码组成,大家看完这篇文章就可以简单的了解html了,多写多练     如果你不致力于成为美工的话,那么作为开发人员,可以读懂HTML.必 ...

  8. UIViewController添加子控制器(addChildViewController)

    // //  TaskHallViewController.m //  yybjproject // //  Created by bingjun on 15/10/27. //  Copyright ...

  9. hadoop 中对Vlong 和 Vint的压缩方法

    hadoop 中对java的基本类型进行了writeable的封装,并且所有这些writeable都是继承自WritableComparable的,都是可比较的:并且,它们都有对应的get() 和 s ...

  10. MapReduce 实现数据join操作

    前段时间有一个业务需求,要在外网商品(TOPB2C)信息中加入 联营自营 识别的字段.但存在的一个问题是,商品信息 和 自营联营标示数据是 两份数据:商品信息较大,是存放在hbase中.他们之前唯一的 ...