智能指针是行为类似于指针的类对象。
为什么需要智能指针? 为了避免当系统异常退出的时候避免资源泄漏(内存)
创建指向str的指针ps。有一个问题。如果函数异常终止,则本地变量将从栈内存中删除——因此ps占据的内存将被释放,如果ps指向的内存也被释放了,则就不需要后面的智能指针了。
或者如果ps有一个析构函数,当ps过期时,调用它的析构函数删除它指向的内存就行。可惜,没有。原因是ps是一个常规指针,不是类对象,故没有析构函数。
这正是智能指针auto_ptr,unique_ptr,shared_ptr的创建思想。三者定义了指针对象,可以将new获得的地址赋给他们。使用他们,需要头文件#include<memory>,且其位于名称空间std中。
void remodel(std::string*str)
{
std::string*ps=new std::strinf(str);
.....
if(weird_thing())
{
throw exception();
str=*ps;
delete ps;
return;
}
智能指针和常规指针的比较
常规指针 智能指针
使用
C++11之后摒弃了auto_ptr,提供 shared_ptr、unique_ptr、weak_ptr三个指针,包含在头文件<memory>中
auto_ptr<double> pd(new double);//double类型的智能指针pd
auto_ptr<string> ps(new string);//string 类型的智能指针ps
unique_ptr<double> pdu(new double);
shared _ptr<string> pss(new string);
auto_ptr<string> ps(new string(“wa haha”);//创建智
能指针时还可将其初始化,也可不初始化。
特点:每个智能指针都有一个explicit构造函数(将指针作为参数),因此不能进行自动转换,必须显示的转换指针和智能指针。
shared_ptr<double> pd;
double *p_reg = new double;
pd = p_reg; // not allowed (implicit conversion)
pd = shared_ptr<double>(p_reg); // allowed (explicit conversion)
shared_ptr<double> pshared = p_reg; // not allowed (implicit conversion)
shared_ptr<double> pshared(p_reg); // allowed (explicit conversion)
auto_ptr
原理是建立所有权概念,对于auto_ptr对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的析构函数会删除该对象,然后让赋值操作转让所有权。
auto_ptr<string> films (new string(“halo”));
auto_ptr<string>pwin;
pwin=films;//films将所有权转让给pwin,此时fimls是一个空指针,不能用它来输出字符串。
若输出*films,则会导致程序崩溃
unique_ptr
与auto_ptr一样,建立所有权概念,但unique_ptr更严格一些
unique_ptr优于auto_ptr,在容器类对象中禁止使用auto_ptr,但可以使用unique_ptr
C++11已经摒弃auto_ptr,原因?
对于auto_ptr上面的程序,如果使用unique_ptr程序不会崩溃。因为:
unique_ptr<string> films (new string(“halo”));
unique_ptr<string>pwin;
pwin=films;//films将所有权转让给pwin
cout<<*films;//编译阶段出现错误
使用unique_ptr时编译出错,与auto_ptr一样,unique_ptr也采用所有权模型,但在使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译器阶段就会出现错误,从而避免了潜在的内存崩溃问题。
使用unique_ptr时应注意的问题:
当我们试图将一个unique_ptr赋给另外一个时
如果源unique_ptr是一个临时右值,编译器将允许这样做,如果源unique_ptr可将存在一段时间,编译器将禁止这样做
unique_ptr<string> pul(new strinf"hi ho!");
unique_ptr<string> pu2;
pu2=pu1;//1 不允许
unique_ptr<string>pu3;
pu3=unique_ptr<string>(new string "yo!");//2 允许
其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而定的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。
shared_ptr
shared_ptr允许多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。
- 初始化。智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。也可以使用make_shared函数初始化。不能将指针直接赋值给一个智能指针,一个是类,一个是指针。例如std::shared_ptr<int> p4 = new int(1);的写法是错误的
- 拷贝和赋值。拷贝使得对象的引用计数增加1,赋值使得原对象引用计数减1,当计数为0时,自动释放内存。后来指向的对象引用计数加1,指向后来的对象。
对于auto_ptr上面的程序,如果使用shared_ptr:
unique_ptr<string> films (new string(“halo”));
unique_ptr<string>pwin;
pwin=films;//films将所有权转让给pwin
cout<<*films;//正确
则可以使用films输出字符串。
此时pwin和films指向同一个对象,引用计数从1增加到2,在程序结尾,后创建的pwin首先调用析构函数,析构函数将引用计数减1,然后films被释放,再次调用析构函数,引用计数减到0。
weak_ptr
weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为
1、没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。
2、weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。
3、但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。
4、使用weak_ptr的成员函数use_count()可以观测资源的引用计数
另一个成员函数expired()的功能等价于use_count()==0, 判断引用计数是否为空。
5、weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。
#include <iostream>
#include <memory>
int main() {
{
std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);
std::cout << sh_ptr.use_count() << std::endl;
std::weak_ptr<int> wp(sh_ptr);
std::cout << wp.use_count() << std::endl;
if(!wp.expired()){
std::shared_ptr<int> sh_ptr2 = wp.lock(); //get another shared_ptr
*sh_ptr = 100;
std::cout << wp.use_count() << std::endl;
}
}
//delete memory
}
总结:
使用new 分配内存时,才能使用auto_ptr和shared_ptr,但使用new[]分配内存时,不能使用他们
使用new和new[]分配内存时,才能使用unique_ptr.
选择智能指针
1) 如果程序要使用多个指向同一个对象的指针,应选择shared_ptr.
如:有一个指针数组,并使用一些辅助指针来标识特定元素。
两个对象包含都指向第三个对象的指针。
STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。
2)如果程序不需要多个指向同一对象的指针,则可以使用unique_ptr。如果函数使用new分配内存,且返回指向该内存的指针,则将其返回类型声明为unique_ptr。如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())。例如,可在程序中使用类似于下面的代码段。
unique_ptr<int> make_int(int n)
{
return unique_ptr<int>(new int(n));
}
void show(unique_ptr<int> &p1)
{
cout << *a << ' ';
}
int main()
{
...
vector<unique_ptr<int> > vp(size);
for(int i = 0; i < vp.size(); i++)
vp[i] = make_int(rand() % 1000); // copy temporary unique_ptr
vp.push_back(make_int(rand() % 1000)); // ok because arg is temporary
for_each(vp.begin(), vp.end(), show); // use for_each()
...
}
其中push_back调用没有问题,因为它返回一个临时unique_ptr,该unique_ptr被赋给vp中的一个unique_ptr。另外,如果按值而不是按引用给show()传递对象,for_each()将非法,因为这将导致使用一个来自vp的非临时unique_ptr初始化pi,而这是不允许的。前面说过,编译器将发现错误使用unique_ptr的企图。
3) 在unique_ptr为右值时,可将其赋给shared_ptr,这与将一个unique_ptr赋给一个需要满足的条件相同。与前面一样,在下面的代码中,make_int()的返回类型为unique_ptr<int>:
unique_ptr<int> pup(make_int(rand() % 1000)); // ok
shared_ptr<int> spp(pup); // not allowed, pup as lvalue
shared_ptr<int> spr(make_int(rand() % 1000)); // ok
模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr。shared_ptr将接管原来归unique_ptr所有的对象。