当我们在编写一些结构复杂的程序的时候,有时候可能需要不断地开辟内存,但是往往就会忘记去释放它,从而造成内存泄露。

使用new和delete来管理动态内存常出的一些错误:

1.忘记delete,即导致了“内存泄漏”,

2.野指针。在对象已经被释放掉之后,(这里注意,此时的指针成为了悬垂指针,即指向曾经存在的对象,但该对象已经不再存在。结果未定义,而且难以检测。)这时候我们再次使用,会产生使用非法内存的指针

    于是乎,就出现了智能指针,RALL资源分配及初始化,其实我觉的,智能指针不仅完成了了这些,它还完成了对象的析构。下面就介绍第一种智能指针,也是最最不提倡的一种Autoptr,它是通过管理权转移的形式管理对象内存的。

(1)创建一个指针P1指向一块内存

(2)创建一个指针p2,将P2指向p1的内存,然后p1置空

这种智能指针之所以不提倡,是因为它的缺陷

1、auto_ptr不能共享所有权。

2、auto_ptr不能指向数组

3、auto_ptr不能作为容器的成员(不太明白)。

4、不要把auto_ptr放入容器

//1

class Autoptr

{

public:

//构造函数

Autoptr(T* ptr):_ptr(ptr)

{}

//析构函数

    ~Autoptr()

{

if(_ptr)

{

cout<<"delete"<<_ptr<<endl;

delete _ptr;

_ptr=NULL;

}

}

//拷贝构造函数

Autoptr(Autoptr<T> &ap):_ptr(ap._ptr)

{

ap._ptr=NULL;

}

//赋值运算符符重载

Autoptr<T>& operator=(Autoptr<T> &ap)

{

if(this!=&ap)

{

delete _ptr;

_ptr=ap._ptr;

ap._ptr=NULL;

}

return *this;

}

//->运算符重载

T* operator->()

{

return _ptr;

}

//*运算符重载

T&operator*()

{

return *_ptr;

}

private:

T* _ptr;

};

上边介绍的其实是Autoptr的新版本,其实还有一种旧的版本

//构造函数

Autoptr(T* ptr):_ptr(ptr)

                   ,_owner(ture)

//析构函数

    ~Autoptr()

{

if(_owner)

{

delete _ptr;

}

}

//拷贝构造函数

Autoptr(Autoptr<T> &ap):_ptr(ap._ptr)

                         ,_owner(true)

{

 ap._owner=false;

}

//赋值运算符符重载

Autoptr<T>& operator=(Autoptr<T> &ap)

{

if(this!=&ap)

{

delete _ptr;

_ptr=ap._ptr;

        _owner=true;

ap._owner=false;

}

return *this;

}

这种Autoptr实现的时候在数据成员里添加了一个owner来管理对象,看似完美无缺,但是在特殊的场景里就会生成野指针哦,请看下面情景

Autoptr<int>ap1(new int(1));

if(xxx)

Autoptr<int>ap2(ap1);

ap1将管理权给了ap2,此时ap1._owner=false,ap2._owner=true;程序结束的时候会对ap2进行delete,但是此时ap1就成了野指针,当我们解引用ap1的时候会发生错误。程序奔溃!!!

上边说了这么多大概就是Autoptr怎么怎么不好,没事千万不要使用它

于是就诞生了比他稍微能好啦么一点的智能指针scopedptr

scopedptr是一个比较霸气而粗暴的指针,它为了保护好自己的成员,将杜绝一切拷贝和赋值,将拷贝构造函数和赋值运算符只声明不实现,其实在这里声明成delete函数也可以,所以它也不能实现多个指针同时维护同一块空间,这里就不多讲了;

最最重要的还算shardptr了

shardptr采用引用计数的方式,可以使多个指针同时维护一块空间,当指向这个空间的最后一个对象被销毁时,指针才会被删除

template <typename T>

class Shardptr

{

public:

//构造函数

Shardptr(T *ptr)

:_ptr(ptr)

,_pcount(new long(1))

{

}

//析构函数

~Shardptr()

{

if(--(*_pcount)==0)

{

delete _ptr;

delete _pcount;

}

}

//拷贝构造函数

Shardptr(Shardptr<T>& sp)

:_ptr(sp._ptr)

,_pcount(sp._pcount)

{

  ++(*_pcount);

}

//赋值运算符重载

Shardptr<T>& operator=(const Shardptr<T>& sp)

{

//sp1=sp1

//sp1=sp2

//sp1=sp3

if(this_ptr!=sp._ptr)

{

if(--(*_pcount)==0)

{

delete _ptr;

delete _pcount;

}

_ptr=sp._ptr;

_pcount=sp._count;

++(*_pcount);

}

}

//

T& operator*()

{

return *_ptr;

}

//

T* operator->()

{

return _ptr;

}

//

long Usecount()

{

return *_pcount;

}

private:

T*_ptr;

long*_pcount;

};


weakptr

到这里只能指针算是掌握,应该是了解的差不多了,不过还有一种智能指针没说,那就是weakptr,它是一个弱指针,主要的任务就是帮助shardptr解决循环引用问题,比如当我们在使用shardptr去删除双向链表的时候,会惊奇的发现根本不会调用析构函数。为啥呢,看图你就会明白

智能指针_指针

这里cur->_next=next;next->_prev=cur;next的prev指向cur,要析构cur ,则需要将next先析构掉,同理想析构next就必须先把cur析构掉,因为这里的cur和Next的引用次数都为2,就这样,造成的后果就是两个节点谁也不能释放;

所以有了weakptr就很好的解决这个问题,它能够保持引用计数不变

#include<iostream>

#include<memory>

using namespace std;

struct Node

{

/*shared_ptr<Node> _next;

shared_ptr<Node> _prev;*/

weak_ptr<Node> _next;

weak_ptr<Node> _prev;

~Node()

{

cout<<"delete"<<this<<endl;

}

};

void TestSharedPtr()

{

shared_ptr<Node> cur(new Node());

shared_ptr<Node> next(new Node());

cout<<"赋值前"<<endl;

cout<<"cur:"<<cur.use_count()<<endl;

cout<<"next:"<<next .use_count()<<endl;

cur->_next=next;

next->_prev=cur;

cout<<"赋值后"<<endl;

cout<<"cur:"<<cur.use_count()<<endl;

cout<<"next:"<<next .use_count()<<endl;

}

智能指针_指针_02

有关智能指针的总结:

(1)Autoptr:实现了管理权转移,最好不用

(2)scopedptr:简单粗暴的守卫智能指针(防拷贝)

(3)sharedptr:实现多个指针共享一块内存,通过增减引用计数,使得最后一个使用它的对象释放它

(4)weakptr:弱指针,辅助shardptr解决循环引用问题