一 C++提供的智能指针总结
为什么需要智能指针?
1)当抛出异常时,我们可能会忘记释放new的指针,导致内存泄漏,或者说较大的工程中,普通情况也会忘记析构指针
2)出现两个指针指向同一块内存时,如果释放时处理不好,会出现释放一个内存两次,或者出现悬垂指针
3)更不用说深拷贝和前拷贝的问题了
所以提出了如下几种智能指针
1 auto_ptr
指针的实现:
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;
}
};
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
弱引用指针
2 unique_ptr
//智能指针的创建
unique_ptr<int> u_i; //创建<span style="font-family:Arial,Helvetica,sans-serif">“</span><span style="font-family:Arial,Helvetica,sans-serif">空智能指针”</span>
u_i.reset(new int(3)); //"绑定”动态对象
unique_ptr<int> u_i2(new int(4));//创建时指定动态对象
//所有权的变化
int *p_i = u_i2.release(); //释放所有权
unique_ptr<string> u_s(new string("abc"));
unique_ptr<string> u_s2 = std::move(u_s); //所有权转移(通过移动语义),u_s所有权转移后,变成“空指针”
u_s2=nullptr;//显式销毁所指对象,同时智能指针变为空指针。与u_s2.reset()等价
可放在容器中(弥补了auto_ptr不能作为容器元素的缺点)
方式一:
- vector<unique_ptr<string>> vs { new string{“Doug”}, new string{“Adams”} };
- vector<unique_ptr<string>>v;
- unique_ptr<string> p1(new string("abc"));
- v.push_back(std::move(p1));//这里需要显式的移动语义,因为unique_ptr并无copy语义
(4) 管理动态数组,因为unique_ptr有unique_ptr<X[]>重载版本,销毁动态对象时调用delete[]
- unique_ptr<int[]> p (new int[3]{1,2,3});
- p[0] = 0;// 重载了operator[]
unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象
auto_ptr有拷贝语义,拷贝后源对象变得无效;unique_ptr则无拷贝语义,但提供了移动语义
auto_ptr不可作为容器元素,unique_ptr可以作为容器元素
auto_ptr不可指向动态数组(尽管不会报错,但不会表现出正确行为),unique_ptr可以指向动态数组
总结:
从上面的例子可以看出,unique_ptr和auto_ptr真的非常类似.其实你可以这样简单的理解,auto_ptr是可以说你随便赋值,但赋值完了之后原来的对象就不知不觉的报废.搞得你莫名其妙.而unique就干脆不让你可以随便去复制,赋值.如果实在想传个值就哪里,显式的说明内存转移std:move一下.然后这样传值完了之后,之前的对象也同样报废了.只不过整个move你让明显的知道这样操作后会导致之前的unique_ptr对象失效.
3shared_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 ;
}
使用shared_ptr需要注意的问题
1)不要用一个原始指针初始化多个shared_ptr,原因在于,会造成二次销毁,如下所示:
int *p5 = new int; std::shared_ptr<int> p6(p5); std::shared_ptr<int> p7(p5);// logic error
1、创建shared_ptr实例
最安全和高效的方法是调用make_shared库函数,该函数会在堆中分配一个对象并初始化,最后返回指向此对象的share_ptr实例。如果你不想使用make_ptr,也可以先明确new出一个对象,然后把其原始指针传递给share_ptr的构造函数。
示例如下:
2 拷贝和赋值操作
我们可以用一个shared_ptr对象来初始化另一个share_ptr实例,该操作会增加其引用计数值。
shared_ptr < string > pStr2(pStr);如果shared_ptr实例p和另一个shared_ptr实例q所指向的类型相同或者可以相互转换,我们还可以进行诸如p = q这样赋值操作。该操作会递减p的引用计数值,递增q的引用计数值。
4 weak_ptr
http://blog.csdn.net/Xiejingfa/article/details/50772571