为什么要引入智能指针:
在C++中,动态内存的管理一般是用一对运算符完成的:new和delete,
new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针,
delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。
int *b=new int(5);
delete(b);
使用new和delet动态内存管理经常会出现问题: ( ̄へ ̄)
忘记释放内存,会造成内存泄漏;
尚有指针引用内存的情况下就释放了它,产生引用非法内存的指针。
一块内存释放两次。
等。。。
所以,为了更加容易(更加安全)的使用动态内存,引入了智能指针的概念。
智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。 o(≧口≦)o
智能指针概念:
通俗的说:智能指针其实就是一个类,这个类的构造函数中传入一个普通指针,析构函数中释放传入的指针。智能指针的类都是栈上的对象,所以当函数(或程序)结束时会自动被释放。
STL (模板库)一共给我们提供了四种智能指针:
auto_ptr、unique_ptr、shared_ptr、weak_ptr,
auto_ptr是C++98提供的解决方案,C+11已将其摒弃,并提出了unique_ptr作为auto_ptr替代方案。虽然auto_ptr已被摒弃,但在实际项目中仍可使用,但建议使用较新的unique_ptr,因为unique_ptr比auto_ptr更加安全,后文会详细叙述。
shared_ptr和weak_ptr则是C+11从准标准库Boost中引入的两种智能指针。
weak_ptr被设计为与shared_ptr共同工作,weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手。
此外,Boost库还提出了boost::scoped_ptr、boost::scoped_array、boost::intrusive_ptr 等智能指针,虽然尚未得到C++标准采纳,但是实际开发工作中可以使用。(‾◡◝)
Boost库是为C++语言标准库提供扩展的一些C++程序库的总称,由Boost社区组织开发、维护。Boost库可以与C++标准库完美共同工作,并且为其提供扩展功能。
智能指针思想
当两个指针a、b都指向同一个对象时,a过期时删除对象一次,b过期时删除对象一次,所以等于删除了同一个对象两次。有什么解决办法呢? (-∀=)
- 定义赋值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本,缺点是浪费空间,所以智能指针都未采用此方案。
- 建立所有权(ownership)概念。对于特定的对象,只能有一个智能指针可拥有,这样只有拥有对象的智能指针的析构函数会删除该对象。然后让赋值操作转让所有权。这就是用于auto_ptr和unique_ptr 的策略,但unique_ptr的策略更严格。
- 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加1,而指针过期时,计数将减1,。当减为0时才调用delete。这是shared_ptr采用的策略。
auto_ptr 和 unique_ptr
auto_ptr 是STL中智能指针家族的成员之一,由C++98引入,定义在头文件。其功能和用法类似于unique_ptr,由 new expression 获得对象,在 auto_ptr 对象销毁时,他所管理的对象也会自动被 delete 掉。
auto_ptr和unique_ptr都是“独占”所指向的对象。 ( ﹁ ﹁ ) ~→
类似这样:
首先a指向object:
再让b指向object:
可以看到,b自动独占了object的对象。
那么为什么c++11抛弃了auto_ptr而换成了unique_ptr呢?
是因为如果使用auto_ptr,当让b指向object后,如果出现无意识使用了a,那么就会出现程序崩溃。
就像这样:
auto_ptr<string> a (new string("hello"));
auto_ptr<string> b;
b=a; // 将所有权从a转让给b,此时a不再引用该字符串从而变成空指针
cout<< *b <<endl;//程序正确
cout<< *a <<endl;//程序崩溃,段错误
那么,难道使用unique_ptr就可以避免了吗?
可以试一试:
unique_ptr<string> a (new string("hello"));
unique_ptr<string> b;
b=a; //报错,编译不通过
在使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译期就直接报错。
指导你发现潜在的内存错误。这就是为何要摒弃auto_ptr的原因,一句话总结就是:避免因潜在的内存问题导致程序崩溃。 <( ̄︶ ̄)>
从上面可见,unique_ptr比auto_ptr更加安全,因为auto_ptr有拷贝语义,拷贝后原象变得无效,再次访问原对象时会导致程序崩溃;unique_ptr则禁止了拷贝语义,但提供了移动语义,即可以使用 std::move() 进行控制权限的转移,如下代码所示:
b=std::move(a); //编译通过
但是,在使用std::move将unique_ptr的控制权限转移后,不能够再通过unique_ptr来访问和控制资源了,否则同样会出现程序崩溃。所以,如果继续执行 cout<< *a <<endl 还是会段错误。
我们可以在使用unique_ptr访问资源前,使用成员函数get()进行判空操作。
if(a.get()!=NULL)
{
cout<< *a <<endl;
}//程序通过
除了这些,unique_ptr比auto_ptr还多了一些功能,比如:
unique_ptr可放在容器中,弥补了auto_ptr不能作为容器元素的缺点。
自定义资源删除操作(Deleter)。unique_ptr默认的资源删除操作是delete/delete[],若需要,可以进行自定义。
等。。。
综上所述:由于以上这些优点,unique_ptr代替了auto_ptr。 ╰( ̄▽ ̄)╭
shared_ptr 和 weak_ptr
unique_ptr:“独占”所指向的对象。
shared_ptr:允许多个指针指向同一个对象。
shared_ptr 是一个标准的共享所有权的智能指针,允许多个指针指向同一个对象,定义在 memory 文件中,命名空间为 std。shared_ptr最初实现于Boost库中,后由C++11引入到C++ STL。
shared_ptr利用引用计数的方式实现了对所管理的对象的所有权的分享,即允许多个shared_ptr共同管理同一个对象。像shared_ptr这种智能指针,《Effective C++》称之为“引用计数型智能指针”
shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,
shared_ptr 对象除了包括一个所拥有对象的指针外,还必须包括一个引用计数代理对象的指针;
智能指针(smart pointer)
的一种通用实现技术是使用引用计数(reference count)。
智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。
引用计数是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。
shared_ptr 的大致步骤为: (。・∀・)ノ゙
- 当创建智能指针类的新对象时,初始化指针,并将引用计数设置为1;
- 当能智能指针类对象作为另一个对象的副本时,拷贝构造函数复制副本的指向辅助类对象的指针,并增加辅助类对象对基础类对象的引用计数(加1);
- 使用赋值操作符对一个智能指针类对象进行赋值时,处理复杂一点:先使左操作数的引用计数减1(为何减1:因为指针已经指向别的地方),如果减1后引用计数为0,则释放指针所指对象内存。然后增加右操作数所指对象的引用计数(为何增加:因为此时做操作数指向对象即右操作数指向对象)。
- 完成析构函数:调用析构函数时,析构函数先使引用计数减1,如果减至0则delete对象。
shared_ptr 也有判空函数,之前unique_ptr的get判空在这里也可以使用
shared_ptr<string> a (new string("hello"));
if(a.get()!=NULL)
{
cout<< *a <<endl;
}
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。
由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock,此函数检查weak_ptr指向的对象是否存在。如果存在,lock返回一个指向共享对象的shared_ptr,如果不存在,lock将返回一个空指针。
weak_ptr被设计为与shared_ptr共同工作,可以从一个shared_ptr或者另一个weak_ptr对象构造而来。weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator*和->,因此取名为weak,表明其是功能较弱的智能指针。它的最大作用在于协助shared_ptr工作,可获得资源的观测权,像旁观者那样观测资源的使用情况。观察者意味着weak_ptr只对shared_ptr 进行引用,而不改变其引用计数,当被观察的shared_ptr失效后,相应的weak_ptr也相应失效。
shared_ptr<int> sp(new int(10));
assert(sp.use_count() == 1);
weak_ptr<int> wp(sp); //从shared_ptr创建weak_ptr
assert(wp.use_count() == 1);
weak_ptr的作用:
weak_ptr看似没有什么作用。 (⊙﹏⊙)
其实weak_ptr可用于打破循环引用。引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象。
就像这样:
还有一个代码也很说明问题:
class AA
{
public:
AA() { cout << "AA::AA() 开始" << endl; }
~AA() { cout << "AA::~AA() 结束" << endl; }
shared_ptr<BB> bb;
//weak_ptr<BB> bb;
};
class BB
{
public:
BB() { cout << "BB::BB() 开始" << endl; }
~BB() { cout << "BB::~BB() 结束" << endl; }
shared_ptr<AA> aa;
//或者 weak_ptr<AA> aa;
};
int main()
{
shared_ptr<AA> ptr_a (new AA);
shared_ptr<BB> ptr_b ( new BB);
cout << "ptr_a use_count: " << ptr_a.use_count() << endl;
cout << "ptr_b use_count: " << ptr_b.use_count() << endl;
//下面两句导致了AA与BB的循环引用,结果就是AA和BB对象都不会析构
ptr_a->bb = ptr_b;
ptr_b->aa = ptr_a;
cout << "ptr_a 引用记数值: " << ptr_a.use_count() << endl;
cout << "ptr_b 引用记数值: " << ptr_b.use_count() << endl;
}
类AA中有一个类BB的指针,类BB中有一个类AA的指针。
两个对象在相互引用后,两个指针的记数值都变为了2(初始值的1,加上对方引用的1)
这样的话,aa析构的条件是bb结束引用,bb析构的条件是aa结束引用,双方陷入死循环。
所以两个对象都不会被析构,运行结果:
可以看出类没有被析构。
修改办法就是将其中的一个指针由 shared_ptr改为weak_ptr,这样由于weak_ptr不增加引用值,所以可以结束被析构。
运行结果:
But…其实我不知道为什么有人要这样很绕的写代码…( _ _)ノ|
后记
关于智能指针有优点当然也有缺点,比如有很多的陷阱:
(1)不使用相同的内置指针值初始化(或reset)多个智能指针。
(2)不delete get()返回的指针
(3)不使用get()初始化或reset另一个智能指针
(4)如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了
(5)如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器
等等。。。
由于只是初探,所以在这里就不详细描述了(ps:其实是因为有的我还不太会) (;´д`)ゞ
最后,好好学习,天天向上。 ヾ( ̄▽ ̄)ByeBye
参考资料:
STL四种智能指针
c++ prime