智能指针(一):STL auto_ptr实现原理
2015-09-06 22:22
225 查看
智能指针实际上是一个类(class),里面封装了一个指针.它的用处是啥呢?
但如果是在堆(heap)中分配了内存,也就是用malloc或者new.那只能自动手动的使用free或delete去释放.所以使用heap时处理的不好很容易出现啥内存泄露(内存没有释放掉).或者如果你delete一次了,但没让它赋值为0,然后再delete一次就导致未定义行为了.
于是你想如果系统能也像管理stack一样来管理你的heap区域.不用再担心内存的分配与释放该多好啊.事实上Java,C#都这样去做了.也给你去管理heap区域了(所有的自定义类型实例化时都去heap区域获取内存了.也没于提供指针的功能.你想自己去释放内存都不给你这机会了.JVM或者CLR会在后台自动的去给你释放掉那些不用的内存.当然这样一来效率自然没有你手动释放来的高了.)
假如有一个指针指向一块分配的内存,智能指针就是把该指针封装起来,然后用完了会自动去释放内存(通过智能指针类的析构函数).这样你就不用担心没有去释放内存了.当然并不是说你使用了智能指针就能像使用Java,C#一样不用再担心内存问题了.智能指针在使用的时候还会存在很多的问题.
据说JVM,CRL也是用(c与c++)实现的.不知道那里面也有用到智能指针不.
使用上面的代码:
这样一来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
弱引用指针
指针与内存
说到指针自然涉及到内存.我们如果是在堆栈(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.基础版本(构造函数和析构函数)
#include<iostream> using namespace std; template<class T> class my_autoptr { public: T *m_ptr; public: my_autoptr(T* ptr=NULL) :m_ptr(ptr) { cout << "my_autoptr()" << endl; } ~my_autoptr() { cout << "~my_autoptr()" << endl; delete m_ptr; } }; int main() { my_autoptr<int> myptr = new int(1); // 等价int* ip = new int(1)但这样你得手动delete ip;而用了智能指针就不用手动delete了. cout << *myptr.m_ptr << endl; //相当于cout<<*ip; return 0; }
2.改进版本(重载运算符使类用起来像指针)
上面的的精简版本用起来还挺麻烦.我们是希望封装了指针类用起来跟指针本身一样才好.所以需要重载-> , *等运算符#include<iostream> using namespace std; template<class T> class my_autoptr { private: T * m_ptr; public: my_autoptr(T*ptr) :m_ptr(ptr){} ~my_autoptr() { delete m_ptr; } T& operator*()const { return *m_ptr; } T* operator->()const { return m_ptr; } }; class AlienWare { public: void Run() { cout << "I am Running" << endl; } void Show() { cout << "I am AlienWare" << endl; } }; int main() { my_autoptr<AlienWare> mptr = new AlienWare(); mptr->Show(); (*mptr).Run(); return 0; }
3.完善版本(复制构造)
一个完善点的类往往还涉及到拷贝构造函数的一些操作.也可以做把另外一个智能指针类做为构造函数的参数,或者通过=给一个类赋值#include<iostream> using namespace std; template<class T> class my_autoptr { private: T * m_ptr; T *GetPtr() { T*tmp = m_ptr; m_ptr = NULL; return tmp; } public: explicit my_autoptr(T*ptr=NULL) :m_ptr(ptr){} my_autoptr(my_autoptr &mp) { m_ptr = mp.GetPtr(); } my_autoptr& operator=(my_autoptr& ap) { if (&ap != this) { delete m_ptr; m_ptr = ap.GetPtr(); } return *this; } ~my_autoptr() { delete m_ptr; } T& operator*()const { return *m_ptr; } T* operator->()const { return m_ptr; } void reset(T* p) { //指针重置,相当于把指针指向另外一个地方去 if (p != m_ptr) delete m_ptr; m_ptr = p; } }; class AlienWare { private: string name; public: AlienWare(string name="") :name(name){} AlienWare(AlienWare& a) { name = a.name; } AlienWare &operator = (const AlienWare&a) { if (&a != this) { name = a.name; } return *this; } ~AlienWare() { } string GetName() { return name; } void Run() { cout << "I am Running" << endl; } void Show() { cout << "I am AlienWare" << endl; } }; int main() { my_autoptr<AlienWare> mptr(new AlienWare()); mptr->Show(); (*mptr).Run(); my_autoptr<AlienWare> mptr1(mptr);//call copy construct my_autoptr<AlienWare> mptr2(new AlienWare("AlienWare")); mptr2 = mptr1;//call "=" assignment //此时mptr1对象中的指针是NULL //mptr1->GetName();//重载了->,但是指针为空。此处出现奔溃 mptr1->Show();//竟然没有是,所以尝试了下下面的代码。空指针可有调用Show的方法 AlienWare *p = NULL; p->Show(); AlienWare a1("AlienWare"); AlienWare a2(a1); a2=a1; return 0; }
4.源码(C++ STL的auto_ptr实现(位于memory头文件中))
// auto_ptr implementation -*- C++ -*- // Copyright (C) 2007, 2008, 2009, 2010 Free Software Foundation, Inc. // // This file is part of the GNU ISO C++ Library. This library is free // software; you can redistribute it and/or modify it under the // terms of the GNU General Public License as published by the // Free Software Foundation; either version 3, or (at your option) // any later version. // 代码地址:http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-api-4.5/a00745_source.html #ifndef _BACKWARD_AUTO_PTR_H #define _BACKWARD_AUTO_PTR_H 1 template<typename _Tp> class auto_ptr { private: _Tp* _M_ptr; public: /// The pointed-to type. typedef _Tp element_type; explicit auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { } auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { } template<typename _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) throw() : _M_ptr(__a.release()) { } auto_ptr& operator=(auto_ptr& __a) throw() { reset(__a.release()); return *this; } template<typename _Tp1> auto_ptr& operator=(auto_ptr<_Tp1>& __a) throw() { reset(__a.release()); return *this; } ~auto_ptr() { delete _M_ptr; } element_type& operator*() const throw() { //就相当于一个Assert _GLIBCXX_DEBUG_ASSERT(_M_ptr != 0); return *_M_ptr; } element_type* operator->() const throw() { _GLIBCXX_DEBUG_ASSERT(_M_ptr != 0); return _M_ptr; } element_type* get() const throw() { return _M_ptr; } element_type* release() throw() { element_type* __tmp = _M_ptr; _M_ptr = 0; return __tmp; } void reset(element_type* __p = 0) throw() { if (__p != _M_ptr) { delete _M_ptr; _M_ptr = __p; } } }; #endif /* _BACKWARD_AUTO_PTR_H */
使用上面的代码:
#include<iostream> #include"auto_ptr.h" using namespace std; class AlienWare { private: string name; public: AlienWare(string name = "") :name(name){} AlienWare(AlienWare& a) { name = a.name; } AlienWare &operator = (const AlienWare&a) { if (&a != this) { name = a.name; } return *this; } ~AlienWare() { } string GetName() { return name; } void Run() { cout << "I am Running" << endl; } void Show() { cout << "I am AlienWare" << endl; } }; int main() { my_auto_ptr<AlienWare> ptr(new AlienWare()); ptr->Show(); my_auto_ptr<AlienWare> ptr1(ptr);//ptr失去所有权,被包装的指针指向空 return 0; }
5.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
弱引用指针
相关文章推荐
- JDBC 连接SQLSERVER2008数据库
- Myeclipse无法弹出javax包下的所有类的提示---解决方法
- 计算机感想一
- js中arguments的用法
- poi 导出模板设置某列格式存储身份证号
- MySQL(十五)之基于ssl加密搭建含有gtid特性的MySQL主从复制
- MySQL 错误日志 Error log
- 数据结构之---C语言实现冒泡排序
- 转:Java eclipse下 Ant build.xml实例详解
- SharedPreferences
- shell学习
- 51nod 1080:两个数的平方和
- 【数据结构】邻接表的3种常用表示方式——C++描述
- 如何用 js 递归输出树型
- 和浙大妹子聊准备笔面
- 51nod 1080:两个数的平方和
- 从源码编译rpi的内核
- Java:单例模式的七种写法
- 算法学习日记 -----链表以及箱子排序算法
- hdu3416 最短路+最大流