智能指针(一):STL auto_ptr实现原理

智能指针实际上是一个类(class),里面封装了一个指针.它的用处是啥呢?

指针与内存

说到指针自然涉及到内存.我们如果是在堆栈(stack)中分配了内存,用完后由系统去负责释放.如果是自定义类型,就会自动的去调用你的析构函数.

但如果是在堆(heap)中分配了内存,也就是用malloc或者new.那只能自动手动的使用free或delete去释放.所以使用heap时处理的不好很容易出现啥内存泄露(内存没有释放掉).或者如果你delete一次了,但没让它赋值为0,然后再delete一次就导致未定义行为了.

于是你想如果系统能也像管理stack一样来管理你的heap区域.不用再担心内存的分配与释放该多好啊.事实上Java,C#都这样去做了.也给你去管理heap区域了(所有的自定义类型实例化时都去heap区域获取内存了.也没于提供指针的功能.你想自己去释放内存都不给你这机会了.JVM或者CLR会在后台自动的去给你释放掉那些不用的内存.当然这样一来效率自然没有你手动释放来的高了.)

假如有一个指针指向一块分配的内存,智能指针就是把该指针封装起来,然后用完了会自动去释放内存(通过智能指针类的析构函数).这样你就不用担心没有去释放内存了.当然并不是说你使用了智能指针就能像使用Java,C#一样不用再担心内存问题了.智能指针在使用的时候还会存在很多的问题.

据说JVM,CRL也是用(c与c++)实现的.不知道那里面也有用到智能指针不.

智能指针的实现

如果你自己要封装一个指针你会咋整呢?来来个初稿瞧瞧

1.最精简版本

template< class T>

class my_auto_ptr {

public:

T* m_ptr;  //被封装的指针

public:

my_auto_ptr( T* p) :m_ptr( p ) { }   //构造函数

~my_auto_ptr() { delete m_ptr; }     //析构函数

}

上面的自然是最精简版的,只一个成员变量,构造函数和析造函数.不过虽然简单其实也能拿来用了啊.比如:

my_auto_ptr<int> myPtr( new int(88) ); //等价int* ip = new int(88); 但这样你得手动delete ip;而用了智能指针就不用手动delete了.

cout<< *myPtr.m_ptr;   //相当于cout<<*ip;

2.改进版本(重载运算符使类用起来像指针)

上面的的精简版本用起来还挺麻烦.我们是希望封装了指针类用起来跟指针本身一样才好.所以需要重载-> , * 等运算符

template< class T>

class my_auto_ptr {

private:

T* m_ptr; //被封装的指针

public:

my_auto_ptr( T* p) :m_ptr( p ) { }

~my_auto_ptr() { delete m_ptr; }

T& operator*() { return *m_ptr;}

T*  operator->() { return m_ptr;}

}

现在my_auto_ptr可以变得很像指针了

my_auto_ptr<int> mp(new int(88) );   //等价int* ip = new int(88);

int num = *mp;   //等价int num = *ip;

假如有这样的类struct Arwen { void Test() { cout<"i am arwen"<<; }

则my_auto_ptr<Arwen> mp( new Arwen);   //等价Arwen* ip = new Arwen;

mp->Test();  //等价ip-Test();

3.完善版本(复制构造)

一个完善点的类往往还涉及到复制构造的一些操作.也可以做把另外一个智能指针类做为构造函数的参数,或者通过=给一个类赋值

template< class T>

class my_auto_ptr {

private:

T* m_ptr;

T* GetPtr(){ //供构造赋值时使用

T* tmp = m_ptr;

m_ptr = 0;

return tmp;

}

public:

explicit my_auto_ptr( T* p = 0) :m_ptr( p ) { }

~my_auto_ptr() { delete m_ptr; }

T& operator*() { return *m_ptr;}

T* operator->() { return m_ptr;}

my_auto_ptr(my_auto_ptr& mp){   //复制构造函数

m_ptr = mp.GetPtr(); //mp复制过来后它自己原来的指针相当于失效了.

}

my_auto_ptr& operator=(my_auto_ptr& ap){ 造型赋值操作符

if(ap != *this)

{

delete m_ptr;

m_ptr = ap.GetPtr();

}

return *this;

}

void reset(T* p){  //指针重置,相当于把指针指向另外一个地方去

if(p != m_ptr)

delete m_ptr;

m_ptr = p;

}

};

使用举例:

如有类struct Arwen{

int age;

Arwen(int gg) :age(gg) { };

};

void main()

{

 my_auto_ptr<Arwen> myPtr( new Arwen(24) );

int num =myPtr->age; //正确

my_auto_ptr<Arwen> ptrOne( myPtr);  //复制构造

//num =myPtr->age; 该处会出错.因为把myPtr复制给ptrOne后,它自己本身相当于失效了

num = ptrOne->age; //正确

my_auto_ptr<Arwen> ptrTwo = ptrOne;

//num = ptrOne->age;该处也会出错,此时ptrOne也失效了

num = ptrTwo->age; //正确

Arwen* pArwen = new Arwen( 88 );

ptrTwo.reset( pArwen);

num = pArwen->age; //此处的值是88了,而不是以前的24

return 0;

}

auto_ptr的缺陷

上面我实现的my_auto_ptr基本上是实现了auto_ptr的所有核心功能.从里面我们可以明显的看到一个很大缺陷.我们看到当通过复构造函数,通过操作符=赋值后,原来的那个智能指针对象就失效了.只有新的智能指针对象可以有效使用了.用个专业点的说法叫所有权的转移.被包装的指针指向的内存块就像是一份独享占用的财产,当通过复制构造,通过=赋值传给别的智能指针对象时,原有的对象就失去了对那块内存区域的拥有权.也就是说任何时候只能有一个智能指针对象指向那块内存区域,不能有两个对象同时指向那块内存区域.

这样一来auto_ptr不能做为STL中容器的元素,为啥呢? 因为容器中经常存在值拷贝的情况嘛,把一个容器对象直接赋值给另一对象.完了之后两个容器对象可得都能用啊.而如果是auto_ptr的话显然赋值后只能一个有用,另一个完全报废了.另外比如你有个变量auto_ptr<int> ap( new int(44) );  然后ap被放进一个容器后,ap就报废不能用了.

不过没办法啊,在c++ 11标准之前,现在我们大部分用的是98标准吧,STL里面只有auto_ptr这一种智能指针.而在11标准中除了auto_ptr还有如下三种:

unique_ptr

smart pointer with unique object ownership semantics

只能有一个主人的指针,可以用于STL容器

shared_ptr

smart pointer with shared object ownership semantics

可共享的指针

weak_ptr

weak reference to an object managed by std::shared_ptr

弱引用指针

智能指针(二):shared_ptr实现原理

前面讲到auto_ptr有个很大的缺陷就是所有权的转移,就是一个对象的内存块只能被一个智能指针对象所拥有.但我们有些时候希望共用那个内存块.于是C++ 11标准中有了shared_ptr这样的智能指针,顾名思义,有个shared表明共享嘛.所以shared_ptr类型的智能指针可以做为STL容器的元素

下面我们来瞧瞧shared_ptr具体是咋实现的.相较auto_ptr有下面几个不同的地方:

1.引进了一个计数器shared_count,用来表示当前有多少个智能指针对象共享指针指向的内存块

2.析构函数中不是直接释放指针对应的内存块,如果shared_count大于1则不释放内存只是将引用计数减1,只是计数等于1时释放内存

3.复制构造与赋值操作符只是提供一般意义上的复制功能,并且将引用计数加1.

shared_ptr实现代码(只实现核心功能)

#include <iostream>

using namespace std;

template<class T>

class shared_ptr{

private:

T* m_ptr; //被封装的指针

unsigned int shared_count;   //引用计数,表示有多少个智能指针对象拥有m_ptr指向的内存块

public:

shared_ptr(T* p):m_ptr(p),shared_count(1){ }

~shared_ptr() { deconstruct();}

void deconstruct(){

if(shared_count == 1)   //引用计数为1表示只有一个对象使用指针指向的内存块了

{

delete m_ptr;

m_ptr = 0;

}

shared_count--;

}

T& operator*() { return *m_ptr;}

T* operator->() { return m_ptr;}

//复制构造函数

shared_ptr(shared_ptr& sp):m_ptr(sp.m_ptr),shared_count(sp.shared_count){

shared_count++;

}

//重载运算符=

shared_ptr& operator = (shared_ptr& sp){

sp.shared_count++;

deconstruct();  //相当于先删掉左值,然后再通过右值赋值.

m_ptr = sp.m_ptr;

shared_count = sp.shared_count;

return *this;

} 

};

使用举例:

如有类struct Arwen{

int age;

Arwen(int gg) :age(gg) { };

};

//下面代码全部运行正确

void main(){

shared_ptr<Arwen> myPtr( new Arwen(24) );

int num =myPtr->age;

shared_ptr<Arwen> ptrOne( myPtr); //复制构造

num =myPtr->age; //如果是auto_ptr该处会出错.因为把myPtr复制给ptrOne后,它自己本身相当于失效了

num = ptrOne->age;

shared_ptr<Arwen> ptrTwo = ptrOne;

num = ptrOne->age;//如果是auto_ptr该处也会出错,此时ptrOne也失效了

num = ptrTwo->age;

return 0;

}

智能指针(三):unique_ptr使用简介

我们知道auto_ptr通过复制构造或者通过=赋值后,原来的auto_ptr对象就报废了.所有权转移到新的对象中去了.而通过shared_ptr可以让多个智能指针对象同时拥有某一块内存的访问权.但假如我们不希望多个内存块被多个智能指针对象共享,同时又不会像auto_ptr那样不知不觉的就让原来的auto_ptr对象失效,可咋整呢?

这个时候就要使用unique_ptr了,顾名思义嘛,unique是唯一的意思.说明它跟auto_ptr有点类似,就是同时只能有一个智能指针对象指向某块内存.但它还有些其他特性.

1.无法进行复制构造与赋值操作.

比如auto_ptr<int> ap(new int(88 );

auto_ptr<int> one (ap) ; // ok

auto_ptr<int> two = one; //ok

但unique_ptr不支持上述操作

unique_ptr<int> ap(new int(88 );

unique_ptr<int> one (ap) ; // 会出错

unique_ptr<int> two = one; //会出错

2.可以进行移动构造和移动赋值操作

就是像上面这样一般意义上的复制构造和赋值或出错.但在函数中作为返回值却可以用.

unique_ptr<int> GetVal( ){

unique_ptr<int> up(new int(88 );

return up;

}

unique_ptr<int> uPtr = GetVal();   //ok

实际上上面的的操作有点类似于如下操作

unique_ptr<int> up(new int(88 );

unique_ptr<int> uPtr2 = std:move( up) ; //这里是显式的所有权转移. 把up所指的内存转给uPtr2了,而up不再拥有该内存.另外注意如果你使用vs2008是没有std:move这函数的.

//vs2010开始才有,是c++ 11标准出现的内容.

3.可做为容器元素

我们知道auto_ptr不可做为容器元素.而unique_ptr也同样不能直接做为容器元素,但可以通过一点间接的手段

例如:

unique_ptr<int> sp(new int(88) );

vector<unique_ptr<int> > vec;

vec.push_back(std::move(sp));

//vec.push_back( sp ); 这样不行,会报错的.

//cout<<*sp<<endl;但这个也同样出错,说明sp添加到容器中之后,它自身报废了.

总结:

从上面的例子可以看出,unique_ptr和auto_ptr真的非常类似.其实你可以这样简单的理解,auto_ptr是可以说你随便赋值,但赋值完了之后原来的对象就不知不觉的报废.搞得你莫名其妙.而unique就干脆不让你可以随便去复制,赋值.如果实在想传个值就哪里,显式的说明内存转移std:move一下.然后这样传值完了之后,之前的对象也同样报废了.只不过整个move你让明显的知道这样操作后会导致之前的unique_ptr对象失效.

智能指针--C++的更多相关文章

  1. enote笔记法使用范例(2)——指针(1)智能指针

    要知道什么是智能指针,首先了解什么称为 “资源分配即初始化” what RAII:RAII—Resource Acquisition Is Initialization,即“资源分配即初始化” 在&l ...

  2. C++11 shared_ptr 智能指针 的使用,避免内存泄露

    多线程程序经常会遇到在某个线程A创建了一个对象,这个对象需要在线程B使用, 在没有shared_ptr时,因为线程A,B结束时间不确定,即在A或B线程先释放这个对象都有可能造成另一个线程崩溃, 所以为 ...

  3. C++智能指针

    引用计数技术及智能指针的简单实现 基础对象类 class Point { public: Point(int xVal = 0, int yVal = 0) : x(xVal), y(yVal) { ...

  4. EC笔记:第三部分:17、使用独立的语句将newed对象放入智能指针

    一般的智能指针都是通过一个普通指针来初始化,所以很容易写出以下的代码: #include <iostream> using namespace std; int func1(){ //返回 ...

  5. 智能指针shared_ptr的用法

    为了解决C++内存泄漏的问题,C++11引入了智能指针(Smart Pointer). 智能指针的原理是,接受一个申请好的内存地址,构造一个保存在栈上的智能指针对象,当程序退出栈的作用域范围后,由于栈 ...

  6. 智能指针unique_ptr的用法

    unique_ptr是独占型的智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr,如下面错误用法: std::unique_pt ...

  7. 基于C/S架构的3D对战网络游戏C++框架 _05搭建系统开发环境与Boost智能指针、内存池初步了解

    本系列博客主要是以对战游戏为背景介绍3D对战网络游戏常用的开发技术以及C++高级编程技巧,有了这些知识,就可以开发出中小型游戏项目或3D工业仿真项目. 笔者将分为以下三个部分向大家介绍(每日更新): ...

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

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

  9. C++11智能指针读书笔记;

    智能指针是一个类对象,而非一个指针对象. 原始指针:通过new建立的*指针 智能指针:通过智能指针关键字(unique_ptr, shared_ptr ,weak_ptr)建立的指针 它的一种通用实现 ...

  10. 「C++」理解智能指针

    维基百科上面对于「智能指针」是这样描述的: 智能指针(英语:Smart pointer)是一种抽象的数据类型.在程序设计中,它通常是经由类型模板(class template)来实做,借由模板(tem ...

随机推荐

  1. windows配置Scrapy爬虫框架

    一.环境 Windows10 64位 Python2.7.13 64位 下面的安装步骤最好配置代理,可能会遇到被墙的情况. 二.Python的安装 可以去参考这篇文章:http://blog.csdn ...

  2. 201621123006 《Java程序设计》第5周学习总结

    1. 本周学习总结 1.1 写出你认为本周学习中比较重要的知识点关键词. 继承.接口.抽象类.多态.interfac.comparable 1.2 尝试使用思维导图将这些关键词组织起来.注:思维导图一 ...

  3. 网站微信登录-python 实现

    最近微信登录开放公测,为了方便微信用户使用,我们的产品也决定加上微信登录功能,然后就有了这篇笔记. 根据需求选择相应的登录方式 微信现在提供两种登录接入方式 移动应用微信登录 网站应用微信登录 这里我 ...

  4. 在.NET开发中的单元测试工具之——NUnit

    NUnit介绍 NUnit是一个专门针对于.NET来写的单元测试框架,它是xUnit体系中的一员,在xUnit体系中还有针对Java的JUnit和针对C++的CPPUnit,在开始的时候NUnit和x ...

  5. vue.js 源代码学习笔记 ----- codegenEvents.js

    /* @flow */ const fnExpRE = /^\s*([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/ const simplePathRE = / ...

  6. java并发编程之二--CountDownLatch的使用

    CountDownLatch类 允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助. CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行.使用一个 ...

  7. Hadoop/Spark入门学习笔记(完结)

    Hadoop基础及演练 ---第1章 初识大数据 大数据是一个概念也是一门技术,是在以Hadoop为代表的大数据平台框架上进行各种数据分析的技术. ---第2章 Hadoop核心HDFS Hadoop ...

  8. Hibernate环境搭建+struts整合

    说明:本文档,是和struts2+hibernate的整合示例. lib下还有struts2的jar包,本示例没有显示.struts2的搭建参考struts2的环境搭建 一下载hibernate的ja ...

  9. 安装node

    vuejs:通过npm安装vue框架(印象深刻的问题出在了npm上) 以下所有事我自己在windows系统下遇到的问题 一.安装nodejs node下载地址https://nodejs.org/en ...

  10. WPF 程序 处理未捕获异常,和程序莫名终止说拜拜

    http://www.cnblogs.com/liuyueyu/p/4476151.html 百密一疏的Bug很难避免,没有谁能保证,我的程序永远 0 BUG; 突然接手一个很庞大的项目,在项目运行期 ...