为什么auto_ptr智能指针不能作为STL标准容器的元素

上个星期的博客shared_ptr源码剖析里其实遗漏了一个问题:为什么auto_ptr不可以作为STL标准容器的元素,而shared_ptr可以?  我在网上看了好多篇讲shared_ptr的文章里讲到了这个问题,不过大多文章只是简单两笔带过。我研究了一下这个问题,发现还是有挺多有价值的内容,所以把这个问题单独成一篇博客和大家分享。

先从表象上看看这个问题,假如有这样的一段代码,是否能够运行?

 

int costa_foo()
{vector< auto_ptr<int> > v(10);int i=0;for(;i<10;i++){   v[i]=auto_ptr<int>(new int(i));}   
}

答案是否定的,甚至这段代码是无法编译通过的。g++编译器会报下面这个错误:

 

 

In file included from /usr/include/c++/4.4/memory:51,from foo.cpp:x:
/usr/include/c++/4.4/bits/stl_construct.h: In function ‘void std::_Construct(_T1*, const _T2&) [with _T1 = std::auto_ptr<int>, _T2 = std::auto_ptr<int>]’:
/usr/include/c++/4.4/bits/stl_uninitialized.h:187:   instantiated from ‘static void std::__uninitialized_fill_n<<anonymous> >::uninitialized_fill_n(_ForwardIterator, _Size, const _Tp&) [with _ForwardIterator = std::auto_ptr<int>*, _Size = unsigned int, _Tp = std::auto_ptr<int>, bool <anonymous> = false]’
/usr/include/c++/4.4/bits/stl_uninitialized.h:223:   instantiated from ‘void std::uninitialized_fill_n(_ForwardIterator, _Size, const _Tp&) [with _ForwardIterator = std::auto_ptr<int>*, _Size = unsigned int, _Tp = std::auto_ptr<int>]’
/usr/include/c++/4.4/bits/stl_uninitialized.h:318:   instantiated from ‘void std::__uninitialized_fill_n_a(_ForwardIterator, _Size, const _Tp&, std::allocator<_Tp2>&) [with _ForwardIterator = std::auto_ptr<int>*, _Size = unsigned int, _Tp = std::auto_ptr<int>, _Tp2 = std::auto_ptr<int>]’
/usr/include/c++/4.4/bits/stl_vector.h:1035:   instantiated from ‘void std::vector<_Tp, _Alloc>::_M_fill_initialize(size_t, const _Tp&) [with _Tp = std::auto_ptr<int>, _Alloc = std::allocator<std::auto_ptr<int> >]’
/usr/include/c++/4.4/bits/stl_vector.h:230:   instantiated from ‘std::vector<_Tp, _Alloc>::vector(size_t, const _Tp&, const _Alloc&) [with _Tp = std::auto_ptr<int>, _Alloc = std::allocator<std::auto_ptr<int> >]’
foo.cpp:22:   instantiated from here
/usr/include/c++/4.4/bits/stl_construct.h:74: error: passing ‘const std::auto_ptr<int>’ as ‘this’ argument of ‘std::auto_ptr<_Tp>::operator std::auto_ptr_ref<_Tp1>() [with _Tp1 = int, _Tp = int]’ discards qualifiers

错误出在这一行:

vector< auto_ptr<int> > v(10);

 

 

这个错误是什么含义呢,我们看stl_construct.h的74行所在的函数:

64   /**65    * Constructs an object in existing memory by invoking an allocated66    * object's constructor with an initializer.67    */68   template<typename _T1, typename _T2>69     inline void70     _Construct(_T1* __p, const _T2& __value)71     {72       // _GLIBCXX_RESOLVE_LIB_DEFECTS73       // 402. wrong new expression in [some_]allocator::construct74       ::new(static_cast<void*>(__p)) _T1(__value);75     }

我来直接说这个函数的作用:把第二个参数__T2& value拷贝构造一份,然后复制到T1这个指针所指向的位置。它是如何做到的呢?

 

看第第74行, 这里使用new的方法和我们平常所见到的似乎略有不同。 这是一个placement new。 placement new的语法是:

new(p) T(value)

placement new并不会去堆上申请一块内存,而是直接使用指针p指向的内存,将value对象拷贝一份放到p指向的内存上去。

 

看到这里我就知道为什么编译器在编译本文开头的那段代码时会报这段错误” /usr/include/c++/4.4/bits/stl_construct.h:74: error: passing ‘const std::auto_ptr<int>’ as ‘this’ argument of ‘std::auto_ptr<_Tp>::operator std::auto_ptr_ref<_Tp1>() [with _Tp1 = int, _Tp = int]’ discards qualifiers" 了。通过查看auto_ptr的源代码(位置在 c++头文件目录的 backward/auto_ptr.h)可以发现, auto_ptr并没有一个参数为const auto_ptr& 的拷贝构造函数。换而言之, auto_ptr进行拷贝构造的时候,必需要修改作为参数传进来的那个auto_ptr。

auto_ptr的拷贝构造函数是这样写的:

auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { }

可以看出来, 在拷贝构造一个auto_tr的时候, 必需要把参数的那个auto_ptr给release掉,然后在把参数的_M_ptr指针赋值给自己的_M_ptr指针。补充说明一下, _M_ptr是auto_ptr的一个成员,指向auto_ptr管理的那块内存,也就是在auto_ptr生命周期之外被释放掉的那块内存。

当看到这里的时候,整个问题已经能解释通了。STL容器在分配内存的时候,必须要能够拷贝构造容器的元素。而且拷贝构造的时候,不能修改原来元素的值。而auto_ptr在拷贝构造的时候,一定会修改元素的值。所以STL元素不能使用auto_ptr。

不过,还有一个很重要的问题没有解释。那就是为什么在设计auto_ptr的时候,拷贝构造要修改参数的值呢?

其实这问题很简单,不看代码也可以解释清楚。auto_ptr内部有一个指针成员_M_ptr,指向它所管理的那块内存。而拷贝构造的时候,首先把参数的_M_ptr的值赋值给自己的_M_ptr,然后把参数的_M_ptr指针设成NULL,。如果不这样设计,而是直接把参数的_M_ptr指针赋值给自己的, 那么两个auto_ptr的_M_ptr指向同一块内存,在析构auto_ptr的时候就会出问题: 假如两个auto_ptr的_M_ptr指针指向了同一块内存,那么第一个析构的那个auto_ptr指针就把_M_ptr指向的内存释放掉了,造成后一个auto_ptr在析构时释要放一块已经被释放掉的内存,这明显不科学,会产生程序的段错误而crash掉。

而shared_ptr则不存在这个问题, 在拷贝构造和赋值操作的时候,只会引起公用的引用计数的+1,不存在拷贝构造和赋值操作的参数不能是const的问题。

总结:

1 auto_ptr不能作为STL标准容器的元素。

2 auto_ptr在拷贝复制和赋值操作时,都会改变参数。这是因为两个auto_ptr不能管理同一块内存。

----------------------------------------------------------------------------------

2013年8月16日

 

看来真是有学而时习之的必要。 10分钟之前我竟然在搜这个问题,而全然忘了以前自己研究过。

不过看到stackoverflow上的回答觉得比我写的简单多了, 抄过来:

原文链接

http://stackoverflow.com/questions/111478/why-is-it-wrong-to-use-stdauto-ptr-with-standard-containers

The C++ Standard says that an STL element must be "copy-constructible" and "assignable." In other words, an element must be able to be assigned or copied and the two elements are logically independent.std::auto_ptrdoes not fulfill this requirement.

Take for example this code:

class X
{
};std::vector<std::auto_ptr<X> > vecX;
vecX.push_back(new X);std::auto_ptr<X> pX = vecX[0];  // vecX[0] is assigned NULL

To overcome this limitation, you should use the std::unique_ptr, std::shared_ptr or std::weak_ptr smart pointers or the boost equivalents if you don't have C++11. Here is the boost library documentation for these smart pointers.

 

然后又去翻了一下auto_ptr的源码,  看到operator =的代码这样写的:

223       operator=(auto_ptr& __a) throw()
224       {
225     reset(__a.release());
226     return *this;
227       }

 

多个auto_ptr不能管理同一片内存, 执行=的时候,就把原来的auto_ptr给干掉。其实从逻辑上来讲,如果多个auto_ptr管理同一块内存肯定有问题。第一个auto_ptr析构的时候就应该把内存释放掉了, 第二个auto_ptr再析构的时候,就要去释放已经被释放的内存了, 程序肯定挂掉。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/463013.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【读书笔记】MSDN 上关于加密解密的一个例子

MSDN上的一个不错的例子&#xff1a; 那从内存清除密码的句子有个问题。 需要再看看这个问题到底是怎么回事&#xff0c;怎么解决 cannot convert from Sytem.InPtr to ref string 把下面这句 public static extern bool ZeroMemory(ref string Destination, int Length); 用这…

[luogu3380][bzoj3196]【模板】二逼平衡树【树套树】

题目地址 【洛谷传送门】 题目大意 区间查询k的排名&#xff0c;查找k排名的数&#xff0c;单点修改&#xff0c;区间前驱&#xff0c;区间后继。 感想 真的第一次写树套树&#xff0c;整个人都不对了。重构代码2次&#xff0c;发现样例都过不了&#xff0c;splay直接爆炸&…

Parquet格式描述

背景 2010年 google 发表了一篇论文《Dremel: Interactive Analysis of Web-Scale Datasets》&#xff0c;介绍了其 Dermel 系统是如何利用列式存储管理嵌套数据的&#xff0c;嵌套数据就是层次数据&#xff0c;如定义一个班级&#xff0c;班级由同学组成&#xff0c;同学的信…

Notepad++ 快捷键 大全

修改快捷键的话&#xff1a;设置----管理快捷键----就好比第一个快捷键新建吧--------鼠标双击&#xff0c;然后会弹出来对话框让你对具体想要设置的快捷键进行设置&#xff0c;所有的快捷键都是这么设置的&#xff0c;但是我就是不明白&#xff0c;你要设置的是哪个&#xff1…

Kevin专栏---如何制作试用版安装包

首先需要在http://activationservice.installshield.com/doLogin.do注册一个试用账号。注册完成后系统会自动发送一个15天的试用账号和密码。 在图标Trialware Files上点击鼠标右键&#xff0c;创建一个试用配置项&#xff08;见下图&#xff09;。 首先选择试用文件&#xff0…

ldd命令解析

在linux中&#xff0c;经常会碰到查看可执行文件需要依赖哪些动态链接库&#xff0c;这时ldd命令就可以排上用场了 由于某种原因&#xff0c;屏蔽了一些内容&#xff0c;结果如下&#xff1a; 可以根据结果查找对应的动态链接库

UDP和TCP协议包大小的计算-转

UDP和TCP协议包大小的计算 UDP一次发送数据包的大小&#xff0c;TCP一次发送数据包的大小。MTU最大传输单元&#xff0c;这个最大传输单元实际上和链路层协议有着密切的关系&#xff0c;EthernetII帧的结构DMACSMACTypeDataCRC由于以太网传输电气方面的限制&#xff0c;每个以太…

洛谷 - P1361 - 小M的作物 - 最小割 - 最大权闭合子图

第一次做最小割&#xff0c;不是很理解。 https://www.luogu.org/problemnew/show/P1361 要把东西分进两类里&#xff0c;好像可以应用最小割的模板&#xff0c;其中一类A作为源点&#xff0c;另一类B作为汇点&#xff0c;价值就是边的容量。 然后最小割一定会割断每个中间结点…

LVS

1、安装lvs 在分发器上在172.16.10.1上执行&#xff0c;事先应该配置好你的yum源&#xff0c;保证能够读取介质中的Cluster目录&#xff01;&#xff03;yum install ipvsadm编写&#xff0c;分发规则&#xff08;注意&#xff0c;清空之前的防火墙iptable -F ; iptable -t nat…

linux Swap交换分区概念

Swap交换分区概念 什么是Linux swap space呢&#xff1f;我们先来看看下面两段关于Linux swap space的英文介绍资料: Linux divides its physical RAM (random access memory) into chucks of memory called pages. Swapping is the process whereby a page of memory is copie…

ThinkPHP 数据库操作(七) : 视图查询、子查询、原生查询

视图查询 视图查询可以实现不依赖数据库视图的多表查询&#xff0c;并不需要数据库支持视图&#xff0c;例如&#xff1a; Db::view(User,id,name)->view(Profile,truename,phone,email,Profile.user_idUser.id)->view(Score,score,Score.user_idProfile.id)->where(…

C++中的结构体函数

代码 #include "stdafx.h"structTest{ intnum; Test() { printf("11111111"); } Test(inti) { this->numi; } voidfun() { printf("fun"); }};voidmain( void){ Test a(1); …

Linux 查看进程的命令

1、ps ps -x : 只显示当前用户下的所有进程信息 ps -aux : 所有用户下的进程信息 2、top 显示动态的进程信息&#xff0c;5s刷新一次&#xff1b; 3、htop 需要自己安装htop命令&#xff0c;比较牛&#xff0c;个人也只是简单使用过&#xff0c;比top命令快&#xff0c;可…

关于安卓手机在微信浏览器中无法调起相机的原因

最近功在做公司的一个项目&#xff0c;遇到安卓手机在微信浏览器中更换头像无法调起相机的问题&#xff0c;特来此记录一下。 1.微信没有相机权限&#xff0c;开启就行了。 2.〈input type“file” accept“image/*”/〉。图库和相机都能调起。 3.部分冷门手机因系统原因不开放…

使用Microsoft Media Service实现网络影音多媒体应用系列第三篇---技术要点

技术要点解说&#xff1a; l 对Media Service的引用 Imports Microsoft.WindowsMediaServices.Interop Imports System.Runtime.InteropServices 引入以上两个命名空间以后&#xff0c;就可以看到WMSServer这个类&#xff0c;它就是指向Media Service的类。Activator.CreateIn…

SEO新手入门笔记

2019独角兽企业重金招聘Python工程师标准>>> 上个月公司让我给产品网站做SEO&#xff0c;第一次做这种事情&#xff0c;从中学到一些新东西&#xff0c;在这里做一个总结。 什么是SEO SEO是“搜索引擎优化”的简称&#xff0c;目的是提升网站在搜索引擎结果中的排名…

学习进度(4)

记录时间&#xff1a; 第五周 所花时间&#xff08;包括上课&#xff09; 10h 代码量&#xff08;行&#xff09; 200行 博客量&#xff08;篇&#xff09; 0篇 了解到的知识点 深入学习数据库语句 转载于:https://www.cnblogs.com/quxiangjia/p/10676086.html

linux top 命令的结果

PID&#xff1a;进程标志号&#xff0c;是非零正整数USER&#xff1a;进程所有者的用户名PR&#xff1a;进程的优先级别NI&#xff1a;进程的优先级别数值VIRT&#xff1a;进程占用的虚拟内存值RES&#xff1a;进程占用的物理内存值SHR&#xff1a;进程使用的共享内存值S&#…

从语义开始 – 概念、意义、实践

从语义开始 – 概念、意义、实践http://bbs.blueidea.com/thread-2944769-1-1.html 转载于:https://www.cnblogs.com/javashi/archive/2010/05/21/1741019.html

通过Python脚本理解系统进程间通信

from socket import * #导入socket包中的所有内容from time import ctime #导入time包&#xff0c;同时在本地可使用ctime进行调用import os,sys #导入os&#xff0c;sys包HOSTlocalhost#定义主机PORT21567#定义端口BUFSIZ1024 #定义缓冲区ADDR(HOST,PORT) #定义元组tcpSerSoc…