1、智能指针背后的设计思想

我们先来看一个简单的例子:

void remodel(std::string & str)
{
std::string * ps = new std::string(str);
...
if (weird_thing())
throw exception();
str = *ps;
delete ps;
return;
}

当出现异常时(weird_thing()返回true),delete将不被执行,因此将导致内存泄露。

如何避免这种问题?有人会说,这还不简单,直接在throw exception();之前加上delete ps;不就行了。是的,你本应如此,问题是很多人都会忘记在适当的地方加上delete语句(连上述代码中最后的那句delete语句也会有很多人忘记吧),如果你要对一个庞大的工程进行review,看是否有这种潜在的内存泄露问题,那就是一场灾难!
这时我们会想:当remodel这样的函数终止(不管是正常终止,还是由于出现了异常而终止),本地变量都将自动从栈内存中删除—因此指针ps占据的内存将被释放,如果ps指向的内存也被自动释放,那该有多好啊。
我们知道析构函数有这个功能。如果ps有一个析构函数,该析构函数将在ps过期时自动释放它指向的内存。但ps的问题在于,它只是一个常规指针,不是有析构凼数的类对象指针。如果它指向的是对象,则可以在对象过期时,让它的析构函数删除指向的内存。

这正是 auto_ptr、unique_ptr和shared_ptr这几个智能指针背后的设计思想。我简单的总结下就是:将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。

因此,要转换remodel()函数,应按下面3个步骤进行:

  • 包含头义件memory(智能指针所在的头文件);
  • 将指向string的指针替换为指向string的智能指针对象;
  • 删除delete语句。

下面是使用auto_ptr修改该函数的结果:

# include <memory>
void remodel (std::string & str)
{
std::auto_ptr<std::string> ps (new std::string(str));
...
if (weird_thing ())
throw exception();
str = *ps;
// delete ps; NO LONGER NEEDED
return;
}

2、auto_ptr

智能指针(auto_ptr) 这个名字听起来很酷是不是?其实auto_ptr 只是C++标准库提供的一个类模板,它与传统的new/delete控制内存相比有一定优势,但也有其局限。本文总结的8个问题足以涵盖auto_ptr的大部分内容。

1. auto_ptr是什么?

auto_ptr 是C++标准库提供的类模板,auto_ptr对象通过初始化指向由new创建的动态内存,它是这块内存的拥有者,一块内存不能同时被分给两个拥有者。当auto_ptr对象生命周期结束时,其析构函数会将auto_ptr对象拥有的动态内存自动释放。即使发生异常,通过异常的栈展开过程也能将动态内存释放。auto_ptr不支持new 数组。

2. auto_ptr需要包含的头文件?

#include <memory>

3. 初始化auto_ptr对象的方法?

1) 构造函数

  • 将已存在的指向动态内存的普通指针作为参数来构造

    int* p = new int(33);

    auto_ptr<int> api(p);

  • 直接构造智能指针

    auto_ptr< int > api( new int( 33 ) );

2) 拷贝构造

  利用已经存在的智能指针来构造新的智能指针

  auto_ptr< string > pstr_auto( new string( "Brontosaurus" ) );

  auto_ptr< string > pstr_auto2( pstr_auto );  //利用pstr_auto来构造pstr_auto2

因为一块动态内存只能由一个智能指针独享,所以在拷贝构造或赋值时都会发生拥有权转移的过程。在此拷贝构造过程中,pstr_auto将失去对字符串内存的所有权,而pstr_auto2将其获得。对象销毁时,pstr_auto2负责内存的自动销毁

3) 赋值

  利用已经存在的智能指针来构造新的智能指针

  auto_ptr< int > p1( new int( 1024 ) );

  auto_ptr< int > p2( new int( 2048 ) );

  p1 = p2;

  在赋值之前,由p1 指向的对象被删除。赋值之后,p1 拥有int 型对象的所有权。该对象值为2048。 p2 不再被用来指向该对象。需要注意的是这种情况非常不友好,当我们再试图访问

  p2时(例如cout<<*p2;)这时程序在运行时会报错,原因是p2现在已经不再指向任何有效的内存

4. 空的auto_ptr 需要初始化吗?

通常的指针在定义的时候若不指向任何对象,我们用Null给其赋值。对于智能指针,因为构造函数有默认值0,我们可以直接定义空的auto_ptr如下:

auto_ptr< int > p_auto_int;  //不指向任何对象

5. 防止两个auto_ptr对象拥有同一个对象(一块内存)

因为auto_ptr的所有权独有,所以下面的代码会造成混乱。

int* p = new int(0);
auto_ptr<int> ap1(p);
auto_ptr<int> ap2(p);

因为ap1与ap2都认为指针p是归它管的,在析构时都试图删除p, 两次删除同一个对象的行为在C++标准中是未定义的。所以我们必须防止这样使用auto_ptr。

6. 警惕智能指针作为参数!

1) 按值传递时,函数调用过程中在函数的作用域中会产生一个局部对象来接收传入的auto_ptr(拷贝构造),这样,传入的实参auto_ptr就失去了其对原对象的所有权,而该对象会在函数退出时被局部auto_ptr删除。如下例:

void f(auto_ptr<int> ap)

{cout<<*ap;}
auto_ptr<int> ap1(new int(0));
f(ap1);
cout<<*ap1; //错误,经过f(ap1)函数调用,ap1已经不再拥有任何对象了。

2) 引用或指针时,不会存在上面的拷贝过程。但我们并不知道在函数中对传入的auto_ptr做了什么,如果当中某些操作使其失去了对对象的所有权,那么这还是可能会导致致命的执行期错误。

结论:const reference是智能指针作为参数传递的底线(const auto_ptr<int> &p)。

7. auto_ptr不能初始化为指向非动态内存

原因很简单,delete 表达式会被应用在不是动态分配的指针上这将导致未定义的程序行为。

8. auto_ptr常用的成员函数

1) get()

返回auto_ptr指向的那个对象的内存地址。如下例:

int* p = new int(33);

cout << "the adress of p: "<< p << endl;

auto_ptr<int> ap1(p);

cout << "the adress of ap1: " << &ap1 << endl;

cout << "the adress of the object which ap1 point to: " << ap1.get() << endl;

输出如下:

the adress of p: 00481E00

the adress of ap1: 0012FF68

the adress of the object which ap1 point to: 00481E00

第一行与第三行相同,都是int所在的那块内存的地址。第二行是ap1这个类对象本身所在内存的地址。

2) reset()

重新设置auto_ptr指向的对象。类似于赋值操作,但赋值操作不允许将一个普通指针指直接赋给auto_ptr,而reset()允许。如下例:

auto_ptr< string > pstr_auto( new string( "Brontosaurus" ) );

pstr_auto.reset( new string( "Long -neck" ) );

在例子中,重置前pstr_auto拥有"Brontosaurus"字符内存的所有权,这块内存首先会被释放。之后pstr_auto再拥有"Long -neck"字符内存的所有权。

这种做法可以避免出现利用已经存在的智能指针来构造新的智能指针而造成的问题(上面)。

注:reset(0)可以释放对象,销毁内存。

3) release()

返回auto_ptr指向的那个对象的内存地址,并释放对这个对象的所有权。

用此函数初始化auto_ptr时可以避免两个auto_ptr对象拥有同一个对象的情况(与get函数相比)。

例子如下:

auto_ptr< string > pstr_auto( new string( "Brontosaurus" ) );

auto_ptr< string > pstr_auto2( pstr_auto.get() ); //这是两个auto_ptr拥有同一个对象

auto_ptr< string > pstr_auto2( pstr_auto.release() ); //release可以首先释放所有权

总结:目前新标准的C++已近摒弃了auto_ptr,一块动态内存只能由一个auto_ptr智能指向;一主要原因就是当出现已存在的两个智能指针之间相互赋值或者拷贝时,再次操作原来的auto_ptr智能指针时,会出错。

参考:http://blog.sina.com.cn/s/blog_7708265a01010lyv.html

   https://www.cnblogs.com/lanxuezaipiao/p/4132096.html

【校招面试 之 C/C++】第25题 C++ 智能指针(一)之 auto_ptr的更多相关文章

  1. 【校招面试 之 网络】第3题 HTTP请求行、请求头、请求体详解

    1.HTTP请求报文解剖 HTTP请求报文由3部分组成(请求行+请求头+请求体): 下面是一个实际的请求报文: ①是请求方法,GET和POST是最常见的HTTP方法,除此以外还包括DELETE.HEA ...

  2. 【校招面试 之 网络】第2题 TCP的可靠传输、流量控制、滑动窗口

    1.可靠传输 (1)三次握手 TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接: (1)第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_S ...

  3. 【校招面试 之 C/C++】第27题 C++ 智能指针(三)之 unique_ptr

    auto_ptr<string> p1(new string ("auto") : //#1 auto_ptr<string> p2; //#2 p2 = ...

  4. 【校招面试 之 C/C++】第26题 C++ 智能指针(二)之 share_ptr

    1.综述 shared_ptr 是一个标准的共享所有权的智能指针, 允许多个指针指向同一个对象. 定义在 memory 文件中(非memory.h), 命名空间为 std. shared_ptr 是为 ...

  5. 【校招面试 之 网络】第1题 TCP和UDP

    TCP UDP1.TCP与UDP基本区别  (1)基于连接与无连接  (2)TCP要求系统资源较多,UDP较少:   (3)UDP程序结构较简单(头只有8个字节:源端口号.目标端口号.长度.差错)   ...

  6. 剑指offer 面试25题

    面试25题:题目:合并两个排序的链表 题:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则. 解题思路:递归,并需注意对空链表单独处理. 解题代码: # -* ...

  7. 记2016腾讯 TST 校招面试经历,电面、笔试写代码、技术面、hr面,共5轮

    (出处:http://www.cnblogs.com/linguanh/) 前序: 距离  2016 腾讯 TST 校招面试结束已经5天了,3月27日至今,目前还在等待消息.从投简历到两轮电面,再到被 ...

  8. 墙裂推荐!2020Android阿里&腾讯&百度&字节&美团校招面试汇总

    基本情况 2021届硕士生,Android开发岗 此文主要是2020年年初春招实习的面试和正式校招面试经验汇总,最终校招拿到了腾讯,百度,美团等offer 主要包括阿里4面,腾讯实习4面和校招4面,字 ...

  9. WEB前端面试选择题解答(共36题)

    第1题 ["1", "2", "3"].map(parseInt) A:["1", "2", &qu ...

随机推荐

  1. python appium增加方法

    1.测试过程中发现python client没有拨打电话的方法,因此去添加该方法 1.1查看源码 appium-base-driver/blob/master/lib/protocol/routes. ...

  2. C# 中 PadLeft和PadRight 的用法

    C# 中 PadLeft和PadRight 的用法 在 C# 中可以对字符串使用 PadLeft 和 PadRight 进行轻松地补位. PadLeft(int totalWidth, char pa ...

  3. IIS7根据PID查找对应的站点

    runas /user:administrator cmd cd \Windows\System32\inetsrv appcmd.exe list wp

  4. linux操作系统5 shell编程

    知识内容: 1.shell编程预备知识 2.shell变量 3.表达式与运算符 4.分支循环语句 5.函数 一.shell编程预备知识 1.什么是shell编程 shell是与linux交互的基本工具 ...

  5. Python之函数——内置函数

    内置函数(Built-in Functions) 截止到3.6版本,python一共为我们提供了68个内置函数.它们就是python提供给的可以直接拿来使用的所有函数,接下来让我们一起认识一下这些函数 ...

  6. tornado-输出,request

    3种输出方法:write render redirectimport tornado.ioloop import tornado.web import tornado.httpserver # 非阻塞 ...

  7. Javascript,获取元素,write方法

    一:Javascript:弱类型脚本语言,是一种动态类型.实现部分动画效果和用户交互等 -- html是骨架(页面结构)  css样式  js是行为 -- 弱类型体现: JS代码可以写在body,he ...

  8. MAC CURL : Error:35 SSL certificate problem: Couldn't understand the server certificate format

    起因,使用极光推送最新的版本,里面curl使用https请求,而导致证书出错.一看就懵逼了,从来没遇到过这样的问题,二话不说直接百度,然后就更加懵逼了,搜出来的没有中文内容,对于我这种英文渣来说,简直 ...

  9. S+ hidden tray with window start

    官方论坛上有个帖子给出了答案: I forgot that this is supported in the code, but it requires a little editing of the ...

  10. get提交时中文传值乱码的有关问题

    get提交时中文传值乱码的问题 get提交时中文传值乱码的问题 url=curWarnList.action paramBean.bsIndex=1&paramBean.siteName=萧山 ...