一、什么是智能指针?
智能指针是存储指向动态分配(堆)对象指针的类。除了能够在适当的时间自动删除指向的对象外,他们的工作机制很像C++的内置指针。智能指针在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象。他们也可以用于跟踪被多用户共享的动态分配对象。
二、智能指针的原理
智能指针(smart pointer)的通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。每次创建类的新对象时,初始化指针就将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,析构函数减少引用计数(如果引用计数减至0,则删除基础对象)。
#include <iostream>
template<typename T>
class SmartPtr
{
public:
SmartPtr(T* ptr) :mptr(ptr)
{
flag = true;
}
~SmartPtr()
{
if (flag)
{
delete mptr;
}
mptr = NULL;
}
/*
旧的有释放权 ==》 新的就有
旧的无释放权 ==》 新的无
旧的赋后肯定没有释放权
this->flag = rhs.flag;
rhs.flag = false;
*/
SmartPtr(const SmartPtr<T>& rhs)
{
this->mptr = rhs.mptr;
this->flag = rhs.flag;
rhs.flag = false;
}
SmartPtr<T>& operator=(const SmartPtr<T>& rhs)
{
if (this != &rhs)
{
if (flag)
{
delete mptr;
}
mptr = rhs.mptr;
this->flag = rhs.flag;
rhs.flag = false;
}
return *this;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
T* mptr;
mutable bool flag;//是否拥有释放权 true 有 false 无
};
void Fun(SmartPtr<int> psp)
{
}
int main()
{
SmartPtr<int> sp1 = new int;
SmartPtr<int> sp2 = sp1;
Fun(sp2);//错误
*sp1 = 20;
return 0;
}
三、作用以及优缺点
1、设计简单、效率高。
2、防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也是非常有考究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决。
方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。
四、C++标准库中的智能指针
智能指针在C++11后提供,有:unique_ptr,shared_ptr,weak_ptr 都在boost库中。
C++11前,auto_ptr:一个堆内存只有一块智能指针拥有它的所有权。
旧的有释放权===》新的就有;
旧的没有释放权===》新的就没有;
无论旧的有无,赋值后都没有。
1、auto_ptr的实现
所有权唯一
旧智能指针对象赋给新智能指针对象
取消旧智能指针的所有权
缺点:
所有权转移导致旧智能指针提前失效
#include <iostream>
template<typename T>
class Auto_Ptr
{
public:
Auto_Ptr(T* ptr) :mptr(ptr){}
Auto_Ptr(const Auto_Ptr<T>& rhs)
{
mptr = rhs.mptr;
rhs.Release();
}
Auto_Ptr<T>& operator=(const Auto_Ptr<T>& rhs)
{
if (this != &rhs)
{
delete mptr;//delete NULL;
mptr = rhs.mptr;
rhs.Release();
}
return *this;
}
~Auto_Ptr()
{
if (mptr != NULL)
{
delete mptr;
}
mptr = NULL;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
void Release()const
{
(T*)mptr = NULL;
}
T* mptr;
};
int main()
{
Auto_Ptr<int> ap1 = new int;
Auto_Ptr<int> ap2 = ap1;
*ap1 = 20;
return 0;
}
2、shared_ptr的实现
带有引用计数的智能指针(强智能指针:引用计数都会加)
引用计数:有多少个智能指针对象管理这个堆内存
shared_ptr:多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是=
1、避免循环引用,shared_ptr的一个最大的陷阱是循环引用,循环,循环引用会导致堆内存无法正确释放,导致内存泄漏。
2、一个原始指针不能同时初始化多个shared_ptr,否则会造成二次释放同一内存。
3、初始化时应该注意:智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。也可以使用make_shared函数初始化。不能将指针直接赋值给一个智能指针,一个是类,一个是指针。
4、拷贝和赋值时要注意:拷贝使得对象的引用计数增加1,赋值使得原对象引用计数减1,当计数为0时,自动释放内存。后来指向的对象引用计数加1,指向后来的对象
#include<iostream>
#include <memory>
/*
auto_ptr
一块堆内存 只要有一个智能指针拥有所有权
*/
/*
shared_ptr #include<memory>
weak_ptr
*/
class Ref_Management
{
private:
Ref_Management() :index(0){}
Ref_Management(const Ref_Management&);
public:
static Ref_Management* getInstance()
{
return &rm;
}
public:
void addRef(void* ptr)
{
int idx = FindAddr(ptr);
if (idx < 0)
{
//node[index].ptr = ptr;
//node[index++].ref = 1;
Node tmp(ptr, 1);
node[index++] = tmp;
}
else
{
node[idx].ref++;
}
}
void delRef(void* ptr)
{
int idx = FindAddr(ptr);
if (idx < 0)
{
throw std::exception("addr is not exist!");
}
else
{
if (node[idx].ref != 0)
{
node[idx].ref--;
}
}
}
int getRef(void* ptr)
{
int idx = FindAddr(ptr);
if (idx < 0)
{
throw std::exception("addr is not exist!");
}
return node[idx].ref;
}
private:
int FindAddr(void* ptr)
{
int rt = -1;
for (int i = 0; i < 10; i++)
{
if (node[i].ptr == ptr)
{
rt = i;
break;
}
}
return rt;
}
class Node
{
public:
Node(void* p = NULL, int r = 0) :ptr(p), ref(r){}
public:
void* ptr;//堆内存的地址
int ref; //对应的引用计数
};
Node node[10];
int index;//当前有效个数,当前可以插入的数组有效下标
static Ref_Management rm;
};
Ref_Management Ref_Management::rm;
template<typename T>
class Shared_Ptr
{
public:
Shared_Ptr(T* ptr) :mptr(ptr)
{
prm->addRef(mptr);
}
Shared_Ptr(const Shared_Ptr<T>& rhs) :mptr(rhs.mptr)
{
prm->addRef(mptr);
}
Shared_Ptr<T>& operator=(const Shared_Ptr<T>& rhs)
{
if (this != &rhs)//判字符
{
prm->delRef(mptr);//引用计数减
if (prm->getRef(mptr) == 0)//判断引用计数是否等于0
{
delete mptr;//减为0 ,则释放
}
mptr = rhs.mptr;
prm->addRef(mptr);
}
return *this;
}
~Shared_Ptr()
{
prm->delRef(mptr);
if (prm->getRef(mptr) == 0)
{
delete mptr;
}
mptr = NULL;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
T* mptr;
static Ref_Management* prm;
};
template<typename T>
Ref_Management* Shared_Ptr<T>::prm = Ref_Management::getInstance();
int main()
{
int* p = new int;
Shared_Ptr<int> sp1(p);//sp1.mptr
Shared_Ptr<int> sp2(p);
Shared_Ptr<int> sp3(p);
return 0;
}
3、Scorp_ptr的实现
一个智能指针只能引用一块内存
不允许权限转移
#include <iostream>
template<typename T>
class Scope_Ptr
{
public:
Scope_Ptr(T* ptr) :mptr(ptr){}
~Scope_Ptr()
{
delete mptr;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
Scope_Ptr(const Scope_Ptr<T>&);
Scope_Ptr<T>& operator=(const Scope_Ptr<T>&);
T* mptr;
};
int main()
{
int* p = new int;
Scope_Ptr<int> sp1(p);
Scope_Ptr<int> sp2(p);
Scope_Ptr<int> sp3(p);
//Scope_Ptr<int> sp1 = new int;
//Scope_Ptr<int> sp2 = sp1;
return 0;
}
4、weak_ptr (解决强智能指针遗留的问题(相互引用(造成内存泄漏)))
弱智能指针:只做管理,其他什么都不做。
1、不加引用计数
2、不释放内存
不能单独使用,必须结合强智能指针使用。(配合shared_ptr使用)
weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。
#include <iostream>
#include <memory>
class Ref_Management
{
private:
Ref_Management() :index(0){}
Ref_Management(const Ref_Management&);
public:
static Ref_Management* getInstance()
{
return &rm;
}
public:
void addRef(void* ptr)
{
int idx = FindAddr(ptr);
if (idx < 0)
{
//node[index].ptr = ptr;
//node[index++].ref = 1;
Node tmp(ptr, 1);
node[index++] = tmp;
}
else
{
node[idx].ref++;
}
}
void delRef(void* ptr)
{
int idx = FindAddr(ptr);
if (idx < 0)
{
throw std::exception("addr is not exist!");
}
else
{
if (node[idx].ref != 0)
{
node[idx].ref--;
}
}
}
int getRef(void* ptr)
{
int idx = FindAddr(ptr);
if (idx < 0)
{
throw std::exception("addr is not exist!");
}
return node[idx].ref;
}
private:
int FindAddr(void* ptr)
{
int rt = -1;
for (int i = 0; i < 10; i++)
{
if (node[i].ptr == ptr)
{
rt = i;
break;
}
}
return rt;
}
class Node
{
public:
Node(void* p = NULL, int r = 0) :ptr(p), ref(r){}
public:
void* ptr;//堆内存的地址
int ref; //对应的引用计数
};
Node node[10];
int index;
static Ref_Management rm;
};
Ref_Management Ref_Management::rm;
template<typename T>
class Shared_Ptr
{
public:
Shared_Ptr(T* ptr = NULL) :mptr(ptr)
{
prm->addRef(mptr);
}
Shared_Ptr(const Shared_Ptr<T>& rhs) :mptr(rhs.mptr)
{
prm->addRef(mptr);
}
Shared_Ptr<T>& operator=(const Shared_Ptr<T>& rhs)
{
if (this != &rhs)
{
prm->delRef(mptr);
if (prm->getRef(mptr) == 0)
{
delete mptr;
}
mptr = rhs.mptr;
prm->addRef(mptr);
}
return *this;
}
~Shared_Ptr()
{
prm->delRef(mptr);
if (prm->getRef(mptr) == 0)
{
delete mptr;
}
mptr = NULL;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
T* getPtr()const
{
return mptr;
}
private:
T* mptr;
static Ref_Management* prm;
};
template<typename T>
Ref_Management* Shared_Ptr<T>::prm = Ref_Management::getInstance();
template<typename T>
class Weak_Ptr//弱智能指针的实现
{
public:
Weak_Ptr(T* ptr = NULL) :mptr(ptr){}
Weak_Ptr(const Weak_Ptr<T>& rhs) :mptr(rhs.mptr){}
Weak_Ptr<T>& operator=(const Shared_Ptr<T>& rhs)
{
mptr = rhs.getPtr();
return *this;
}
~Weak_Ptr(){}
private:
T* mptr;
};
class B;
class A
{
public:
A()
{
std::cout << "A()" << std::endl;
}
~A()
{
std::cout << "~A()" << std::endl;
}
public:
Weak_Ptr<B> spa;
};
class B
{
public:
B()
{
std::cout << "B()" << std::endl;
}
~B()
{
std::cout << "~B()" << std::endl;
}
public:
Weak_Ptr<A> spb;
};
int main()
{
Shared_Ptr<A> pa(new A());
Shared_Ptr<B> pb(new B());
pa->spa = pb;
pb->spb = pa;
return 0;
}
5、unique_ptr
unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能得到释放。
unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。