智能指针之atuo_ptr源码剖析

由于c++没有垃圾回收机制,像堆只能手动开辟内存,手动释放,像栈只能系统开辟,系统释放,于是智能指针出现了,它实现了内存的手动开辟,系统释放,防止了内存泄漏问题;

我们知道, 栈对象在离开其作用域的时候, 会自动调用析构函数, 所以, 可以考虑把某一栈对象与某一堆内存绑定,且在其析构函数中释放堆内存,  那么, 在该栈对象离开作用域时, 堆内存自动释放, 这就是智能指针(本质是栈对象)的原理。  这个栈对象装得像指针一样, 所以我们称之为智能指针, 其实, 它不过就是个普通的栈对象而已。

在c++ 98 中只有auto-ptr,这个指针是不完善的,现在几乎都要被摒弃了,但我们还是要去了解一下他的思想,明白为何要被摒弃的呢?以及在c++11上又是如何对他如何改进的呢?

//自动指针即auto_ptr,不同于scoped_ptr指针的是自动指针会转移使用权
//在进行赋值或者拷贝构造之后,原来的auto_ptr会失去对所管指针的拥有权,并且将自己的指针赋为NULL
//同时,在赋值和拷贝构造之后,原来的auto_ptr的指针会指向NULL,也是它最大的弊端之一;

#include <iostream>
using namespace std;

// 简单类
class A
{
public:
void fun()
{

}
};

template<class T>

// 类模板
class auto_ptr
{
public:

// explicit构造函数, 禁止类型转化
explicit auto_ptr(T *p = 0) throw()
: m_bIsOwner(p != 0), m_ptr(p)
{
cout << "debug1" << endl;
}

// owner转移
auto_ptr(const auto_ptr<T>& y) throw()
: m_bIsOwner(y.m_bIsOwner), m_ptr(y.release())
{
cout << "debug2" << endl;
}

// owner转移
auto_ptr<T>& operator=(const auto_ptr<T>& y) throw()
{
cout << "debug3" << endl;

if (this != &y) // 当前对象不是y对象
{
cout << "debug4" << endl;

if (m_ptr != y.get()) // 当前对象绑定的地址不是y对象绑定的地址
{
cout << "debug5" << endl;

if (m_bIsOwner) // 如果当前对象已经绑定堆, 则要先释放
{
cout << "debug6" << endl;
delete m_ptr;
}

cout << "debug7" << endl;

m_bIsOwner = y.m_bIsOwner; // 转移owner
}
else if (y.m_bIsOwner) // 当前对象与y绑定到同一块堆上, 且y是owner, 则把y的owner转移给当前对象
{
cout << "debug8" << endl;

m_bIsOwner = true;
}

cout << "debug9" << endl;

m_ptr = y.release(); // 让y不再是owner
}

cout << "debug10" << endl;

return *this; // 返回当前对象的引用
}

// 析构函数
~auto_ptr()
{
cout << "debug11" << endl;

if (m_bIsOwner) // 只有拥有owner属性才释放堆, 这样避免重复释放
{
cout << "debug12" << endl;

delete m_ptr; // 即使m_ptr是空指针也木有关系
}
}

// 重载对象的*运算符, 使得对象"看起来"像指针, 可以执行*p操作
T& operator*() const throw()
{
cout << "debug13" << endl;

return *get();
}

// 重载对象的->运算符
T *operator->() const throw()
{
cout << "debug14" << endl;

return get();
}

// 获得对象绑定的地址
T *get() const throw()
{
cout << "debug15" << endl;

return m_ptr;
}

// 去掉对象的owner属性
T *release() const throw()
{
cout << "debug16" << endl;

((auto_ptr<T> *)this)->m_bIsOwner = false;
return m_ptr;
}

private:
bool m_bIsOwner; // 对象是否拥有为owner的标志
T *m_ptr; // 对象绑定的指针
};

int main()
{
{
cout << "------------------------------" << endl;

// 用法错误, 因为构造函数中有explicit, 不允许类型转化
//auto_ptr<int> p = new int(10);
}

{
cout << "------------------------------" << endl;

// ok
auto_ptr<int> p(new int(10));
}

{
cout << "------------------------------" << endl;

// 下面代码有严重的运行期错误, 实际上是尝试delete栈上的内容
int a = 10;
//auto_ptr<int> p(&a);
}

{
cout << "------------------------------" << endl;

auto_ptr<int> p(new int(10));

// 错误, p虽然"看似像"指针, 其本质是对象, delete p;是未定义行为
//delete p;
}

{
cout << "------------------------------" << endl;

int *q = new int(10);
auto_ptr<int> p(q);

// 错误, q释放一次, p释放一次, 重复释放啊
//delete q;
}

{
cout << "------------------------------" << endl;

auto_ptr<int> p0;

// 有debug3的打印, 但没有debug4, 知道原因了吧
p0 = p0;
}

{
cout << "------------------------------" << endl;

auto_ptr<int> p0(new int(10));

// 注意, 这是初始化, 不是复制, 所以不会有debug3的打印
auto_ptr<int> p1 = p0;
}

{
cout << "------------------------------" << endl;

auto_ptr<int> p0(new int(10));
auto_ptr<int> p1;

// 注意, 这才是赋值, 所有有debug3, debug4, debug5, debug7, debug9, debug10的打印
// 为什么没有debug6呢? 因为当前对象p1还不是owner
p1 = p0;
}

{
cout << "------------------------------" << endl;

auto_ptr<int> p0(new int(10));
auto_ptr<int> p1(new int(20));

// 有debug6的打印, 因为当先释放p1绑定的对象, 否则内存又泄露了啊
p1 = p0;
}

{
cout << "------------------------------" << endl;

auto_ptr<int> p0(new int(10));

// 把owner转给p1
auto_ptr<int> p1(p0);

// 终于见到你了, debug8
p0 = p1;
}

{
cout << "------------------------------" << endl;

auto_ptr<int> p(new int(10));

// 见到你了, debug13
cout << *p << endl;
}

{
cout << "------------------------------" << endl;

auto_ptr<A> p(new A());

// 终于见到你了, debug15
p->fun();
}

{
cout << "------------------------------" << endl;

auto_ptr<int> p0(new int(10));
auto_ptr<int> p1(p0);
auto_ptr<int> p2(p1);

// 实际上, p3才是最后的winner, 才是最后的owner, 所以释放堆的重任在p3身上
auto_ptr<int> p3(p2);
}

{
cout << "------------------------------" << endl;

// oh, my god, 内存泄露, 本来要delete [] q; 现在析构函数只执行delete q;
int *q = new int[3];
auto_ptr<int> p(q);
}

{
cout << "------------------------------" << endl;

// oh, my god, 内存泄露, 本来要delete [] q; 现在析构函数只执行delete q;
int *q = new int[3];
auto_ptr<int> p(q);

// 已经说过, 下面语句会造成内存重复释放
//delete q;
}

// 最后说明一下, auto_ptr不适合做容器的元素, 这一点我们以后会再次讨论到

return 0;

}

原文链接:https://blog.csdn.net/stpeace/article/details/45155487

测试用例及其好的一篇博客,思路很清晰

原文地址:https://www.cnblogs.com/xcb-1024day/p/11331117.html

时间: 2024-08-02 11:05:18

智能指针之atuo_ptr源码剖析的相关文章

STL&quot;源码&quot;剖析-重点知识总结

STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合套用: 容器(Containers):各种数据结构,如:vector.list.deque.set.map.用来存放数据.从实现的角度来看,STL容器是一种class template. 算法(algorithms):各种常用算法,如:sort.search.copy.erase.从实现的角度来看,STL算法

【转载】STL&quot;源码&quot;剖析-重点知识总结

原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合套用: 容器(Containers):各种数据结构,如:vector.list.deque.set.map.用来存放数据.从实现的角度来看,STL容器是一种class template. 算法(algorithms):各种常用算法,如:sort.se

[转载]《STL源码剖析》阅读笔记之 迭代器及traits编程技法

本文从三方面总结迭代器   迭代器的思想   迭代器相应型别及traits思想   __type_traits思想 一 迭代器思想 迭代器的主要思想源于迭代器模式,其定义如下:提供一种方法,使之能够依序巡防某个聚合物(容 器)所含的元素,而又无需暴露该聚合物的内部表达式.可见她的主要作用便是能够降低耦合,提高代码的 模块性. STL的的中心思想在于:将数据容器和算法分开,彼此独立设计,最后再以一贴胶着剂将它们撮合 在一起,这贴胶着剂便是迭代器.迭代器的行为类似智能指针(例如标准库的auto_pt

std::sort源码剖析

文章参考:源码 这篇文章在一个偶然的机会看到,我原先也是知道sort函数效率高,但终究没有去了解原因,读了这篇文章更加钦佩C++大师积年累月智慧的结晶和对效率的极致追求,看到很多地方不禁暗暗称奇.也还是感慨原文作者对技术的追求和细致的讲解,下面的内容大多来自作者的文章,其中加入了自己的理解,也不枉费大半个下午的时间. 从事程序设计行业的朋友一定对排序不陌生,它从我们刚刚接触数据结构课程开始便伴随我们左右,是需要掌握的重要技能.任何一本数据结构的教科书一定会介绍各种各样的排序算法,比如最简单的冒泡

菜鸟nginx源码剖析 框架篇(一) 从main函数看nginx启动流程(转)

俗话说的好,牵牛要牵牛鼻子 驾车顶牛,处理复杂的东西,只要抓住重点,才能理清脉络,不至于深陷其中,不能自拔.对复杂的nginx而言,main函数就是“牛之鼻”,只要能理清main函数,就一定能理解其中的奥秘,下面我们就一起来研究一下nginx的main函数. 1.nginx的main函数解读 nginx启动显然是由main函数驱动的,main函数在在core/nginx.c文件中,其源代码解析如下,涉及到的数据结构在本节仅指出其作用,将在第二节中详细解释. nginx main函数的流程图如下:

菜鸟nginx源码剖析数据结构篇(六) 哈希表 ngx_hash_t(上)

Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:October 31h, 2014 1.哈希表ngx_hash_t的优势和特点 哈希表是一种典型的以空间换取时间的数据结构,在没有冲突的情况下,对任意元素的插入.索引.删除的时间复杂度都是O(1).这样优秀的时间复杂度是通过将元素的key值以hash方法f映射到哈希表中的某一个位置来访问记录来实现的,即键值为key的元素必定存储在哈希

《STL源码剖析》---stl_tree.h阅读笔记

STL中,关联式容器的内部结构是一颗平衡二叉树,以便获得良好的搜索效率.红黑树是平衡二叉树的一种,它不像AVL树那样要求绝对平衡,降低了对旋转的要求,但是其性能并没有下降很多,它的搜索.插入.删除都能以O(nlogn)时间完成.平衡可以在一次或者两次旋转解决,是"性价比"很高的平衡二叉树. RB-tree(red black tree)红黑树是平衡二叉树.它满足一下规则 (1)每个节点不是红色就是黑色. (2)根节点是黑色. (3)如果节点为红色,则其子节点比为黑色. (4)任何一个节

《STL源码剖析》---stl_iterator.h阅读笔记

STL设计的中心思想是将容器(container)和算法(algorithm)分开,迭代器是容器(container)和算法(algorithm)之间的桥梁. 迭代器可以如下定义:提供一种方法,能够依序寻访某个容器内的所有元素,而又无需暴露该容器的内部表达方式. 在阅读代码之前,要先了解一个新概念:Traits编程技法 template <class T> struct MyIter { typedef T value_type //内嵌型别声明 T *ptr; MyIter(T *p = 0

fasttext源码剖析

fasttext源码剖析 目的:记录结合多方资料以及个人理解的剖析代码: https://heleifz.github.io/14732610572844.html http://www.cnblogs.com/peghoty/p/3857839.html 一:代码总体模块关联图: 核心模块是fasttext.cc以及model.cc模块,但是辅助模块也很重要,是代码的螺丝钉,以及实现了数据采取什么样子数据结构进行组织,这里的东西值得学习借鉴,而且你会发现存储训练数据的结构比较常用的手段,后期可