所谓智能指针就是智能/自动化的管理指针所指向的动态资源的释放,我们知道的智能指针有(C++11标准):
1. auto_ptr
2. scoped_ptr
3. shared_ptr
4. weak_ptr
5. unique_ptr
其中auto_ptr存在问题,一般不会使用,如果确实要使用智能指针的话,scoped_ptr完全可以胜任,在STL容器的对象中,使用shared_ptr、unique_ptr、weak_ptr
auto_ptr
auto_ptr的原理是新的指针在拷贝构造或给原来的指针赋值时,将原指针置空,这样就实现了管理权限的转移,因为其没有深入析构,可能造成内存泄漏,不能指向数组,不能作为容器成员,所以任何情况都不使用。
scoped_ptr
在auto_ptr基础上,将拷贝构造函数与赋值运算符重载声明为私有,这样来达到保护原指针的目的,scoped_ptr对象不能转移指针的所有权,一直独占指针的所有权(除了使用reset成员函数重置内部指针所指的对象,又或者使用swap成员函数交换指针的所指的对象);
(1)使用重载操作符“*”:
scoped_ptr<string> sp(new string("book"));
cout<<*sp<<endl;
(2)使用重载操作符“->”:
cout<<sp->size()<<endl;
(3)使用成员函数reset:
sp.reset(new string("the second"));
cout<<*sp<<endl;
(4)使用swap函数:
scoped_ptr<string> sp2(new string("school"));
sp.swap(sp2);
cout<<"sp's value :"<<*sp<<endl;
cout<<"sp2's value :"<<*sp2<<endl;
unique_ptr
unique_ptr<T>
对象就像一个指向类型T的指针,而且unique_ptr<T>
是排他的,意味着不能与其他unique_ptr<T>
指向同一地址。unique_ptr<T>
对象唯一指向一个对象,独享这个对象的所有权。当一个unique_ptr<T>
对象析构时,它所指的对象也就析构了
(1)生成unique_ptr<T>
一个对象的示例:
std::unique_ptr<std::string> pname {new std::string {"Algernon"}};
更好的生成一个指针对象的方法是利用make_unique<T>()函数,这是一个定义在memory头文件中分的函数模板:
auto pname = std::make_unique<std::string>("Alogernon");
可以通过解引用方式来使用一个对象,就像使用原生指针一样:
std::cout << *pname << std::endl;
(2)不能以传值方式将一个unique_ptr<T>
对象传入函数中,因为它们不支持拷贝,必须使用引用的方式。
- 从一个
unique_ptr<T>
获取一个原生指针
auto unique_p = std::make_unique<std::string>(6, '*');
std::string pstr {unique_p.get()};
类的get()成员函数可以返回一个unique_ptr<T>
所包含的原生指针。
(3)重置unique_ptr<T>
对象
- 当智能指针析构时,
unique_ptr<T>
对象所指的对象也会被析构。调用一个无参的unique_ptr<T>
对象的reset()函数可以析构它所指对象,unique_ptr<T>
对象的原生指针会被替换空指针 - 也可以通过调用
unique_ptr<T>
对象的swap()函数实现交换两个对象:
auto pn1 = std::make_unique<std::string>("Jack");
auto pn2 = std::make_unique<std::string>("Rose");
pn1.swap(pn2); //pn1与pn2所指字符串交换
(4)比较和检查一个unique_ptr<T>
对象
- 比较一个
unique_ptr<T>
对象或者和NULL指针比较时可以用get()转换为原生指针做比较 unique_ptr<T>
可以隐式的转换为布尔值,和一个原生指针的比较检查方式相同,可放在if后面直接检查。
shared_ptr
和unique_ptr<T>
不同的是,多个shared_ptr<T>
可以指向同一地址,因此shared_ptr<T>
允许共享一个对象的所有权。引用计数保存了指向给定地址的shared_ptr<T>
的数量,当引用计数为0时在堆上分配的内存就会被释放。
(1)生成一个shared_ptr<T>
对象的实例:
std::shared_ptr<double> pdata {new double{999.0}};
- 可以通过解引用一个
shared_ptr<T>
指针,使用它所指向的对象或者修改对象的值:*pdata = 8888.0;
- 一个
shared_ptr<T>
对象还可以通过make_shared()函数来得到,效率也高得多:
auto pdata = std::make_shared<double>(999.0);
(2)可以使用一个定义好的指针来初始化一个shared_ptr<T>
指针:
std::shared_ptr<double> pdata2 {pdata};
- pdata2和pdata指向同一个变量,这会导致引用计数的增加。也可以将一个
shared_ptr<T>
指针赋值给另一个shared_ptr<T>
指针:
std::shared_ptr<double> pdata{new double{999.0}};
std::shared_ptr<double> pdata2;
pdata2 = pdata;
当然这会让引用计数增加。 - 和
unique_ptr<T>
一样,可以通过shared_ptr<T>
对象的get()函数得到一个原生指针。
(3)重置shared_ptr<T>
对象
- 如果将一个空指针赋值给
shared_ptr<T>
对象,那么它的地址值会变为空,同样引用计数也会减一,通过成员函数reset()也能达到上述效果。
(4)比较和检查shared_ptr<T>
对象
- 可以使用任意比较运算符来比比较两个
shared_ptr<T>
保存的地址,当然这里的两个指针都是shared_ptr<T>
对象 - 也可以检查
shared_ptr<T>
对象是否有副本
通过成员函数unique(),如果对象实例数为1,返回true,否则返回false - 也可以知道当前有多少个实例:
成员函数use_count()返回当前被调用对象的实例个数。
weak_ptr
weak_ptr<T>
可以从一个shared_ptr<T>
对象创建,它们指向同一个地址。创建一个weak_ptr<T>
不会增加shared_ptr<T>
对象的引用计数,所以它会阻止对象的销毁。当最后一个shared_ptr<T>
引用被释放或者重新指向一个不同的地址时,它们所指向的内存将被释放,即使相关的weak_ptr<T>
可能任然存在
weak_ptr<T>
可以用来解决shared_ptr
的循环引用问题,如下图所示:
删除上图中所有的智能指针或者把它门重置为空指针并不能释放它们所指的对象的内存,因为每个对象仍有一个shared_ptr<X>
指针指向它们,而且也没有外部指针能够访问它们,所以无法被销毁。如果对象使用weak_pte<X>
作为成员变量来指向其他对象,就可以解决这个问题。在外部数组中的指针被销毁或重置时,weak_pte<X>
就不会阻止对象的销毁。
可以使用weak_pte<T>
指针来判断它所指向的对象是否还存在
if(pwData.expired()) std::cout << "Object no longer exists.\n"
如果不存在此函数会返回true