使用boost的智能指针需要包含头文件"boost/smart_ptr.hpp",c++11中需要包含头文件<memory>
1、auto_ptr、scoped_ptr、scoped_array
①、auto_ptr是C++标准中的智能指针,在指针退出作用域的时候自动释放指针指向的内存,即使是异常退出的时候。auto_ptr实际上是一个对象,重载了operator*和operator->,且提供了一些成员函数,比如使用成员get()可以获得对应类型的原始指针。
auto_pt的特点是可以对其进行复制和赋值,但同一时刻只能有一个auto_ptr管理指针。
使用之前需要包含头文件<memory>,eg:
#include <memory> #include <cassert> int main() { std::auto_ptr<int> ap1(new int(5)); cout << *ap1 << endl; int* p = ap1.get(); cout << *p << endl; std::auto_ptr<int> ap2(ap1);//ap1失去管理权,不再拥有指针,ap2得到管理权 assert(ap1.get() == 0);//get()获得的指针为空 return 0; }
②、boost的scoped_ptr用法类似于auto_ptr,都不能用作容器的元素,不支持++、--等指针算数操作。
scoped_ptr的特点是拷贝构造函数和赋值操作符都是私有的,所以scoped_ptr不能进行复制和赋值操作,保证了对对象的唯一管理权。
#include "boost/smart_ptr.hpp" int main() { boost::scoped_ptr<int> sp1(new int(10)); boost::scoped_ptr<int> sp2(sp1);//编译无法通过:不能转让sp1的管理权到sp2 boost::scoped_ptr<int> sp3(new int(30)); sp3 = sp1;//编译无法通过:不能转让sp1的管理权到sp3 return 0; }
③、如果需要指向new[]开辟的内存数组,应该使用scoped_array而不是scoped_ptr。scoped_array构造函数的参数必须是new[]返回的指针,支持使用[]来访问元素,但不支持*、->运算符。scoped_array也不支持拷贝,赋值。
2、shared_ptr、shared_array、weak_ptr
①、概述
shared_ptr没有scoped_ptr的限制,它可以被自由的拷贝和赋值,也可以作为容器的元素。它是一种引用计数型的智能指针,当没有代码使用它时,即引用计数为0的时候自动删除动态分配的内存。
shared_ptr也支持*和->操作符,支持==比较操作(相当于a.get() == b.get()),提供隐式bool类型转换以判断指针的有效性,同样不提供指针的算数运算。
shared_ptr中的一些成员函数:
get():获取内部原始指针。
reset():重置shared_ptr,会导致引用计数减1。不带参数的reset()将shared_ptr重置为不指向任何对象,带参数的reset()用来将shared_ptr重置为指向新的对象。
unique():用来检测当前是不是指针的唯一管理者,即引用计数为1。
use_count():用来获取当前引用计数,但它一般在调试中使用,因为其效率很低,如果只是判断当前引用计数是否为1的话可以使用unique()来替换它。
将shared_ptr赋值为nullptr相当于调用reset()。
shared_ptr也提供了转换运算符dynamic_pointer_cast<>、static_pointer_cast<>、const_pointer_cast<>。
使用示例1:
#include "boost/smart_ptr.hpp" int main() { boost::shared_ptr<int> spInt(new int(10)); if (spInt) //true { spInt.reset(); if (spInt) //false ; } boost::shared_ptr<int> spInt1(new int(5)); //指针引用计数加1,现为1 assert(spInt1.unique()); boost::shared_ptr<int> spInt2(spInt1); //指针的引用计数加1,现为2 boost::shared_ptr<int> spInt3; assert(!spInt3); spInt3 = spInt1; //指针的引用计数加1,现为3 spInt3.reset(); //spInt3被重置,指针的引用计数减1,现为2 assert(!spInt3); return 0; //spInt2退出作用域,指针引用计数减1,为1, spInt1退出作用域,指针引用计数减1,为0,此时释放内存 }
使用示例2:
class Foo; typedef std::shared_ptr<Foo> SPtr; class Foo { }; void func(SPtr sp) { int cnt = sp.use_count(); cout << cnt << endl; } int main() { func(SPtr(new Foo)); //输出为1 SPtr sp(new Foo); func(sp); //输出为2 return 0; }
②、注意事项
shared_ptr不支持直接使用原始指针进行"="操作:
int*p = new int(0); std::shared_ptr<int> sp(p); //std::shared_ptr<int> sp = p; //error ! //sp = p; //error !
对shared_ptr进行 = 操作后原shared_ptr的引用计数会减1,如果变为0的话就会释放其指向的对象,但原对象的释放是在新对象生成之后,稳妥的方法是=操作之前先进行reset操作:
auto sp = std::make_shared<CFoo>(); sp.reset(); //先使用reset()或= nullptr来释放原对象 sp = std::make_shared<CFoo>();
shared_ptr不能多次引用同一原始指针,否则会产生多次释放错误:
CFoo* p = new CFoo; std::shared_ptr<CFoo> sp(p); std::shared_ptr<CFoo> sp2(p); //error !
shared_ptr不能直接包含对象的this指针,如下所示的代码同样会导致两次释放问题:
class Foo { public: virtual ~Foo() { int a = 0; } std::shared_ptr<Foo> getSharedPtr() { return std::shared_ptr<Foo>(this); } }; int main() { std::shared_ptr<Foo> sp(new Foo); std::shared_ptr<Foo> spT = sp->getSharedPtr(); return 0; }
应该使用shared_from_this,需要注意的两点:shared_from_this()方法不能在当前类的构造函数中调用,如果当前对象由一个unique_ptr控制生命,那么对象内不能使用shared_from_this:
class Foo : public std::enable_shared_from_this<Foo> { public: virtual ~Foo() { int a = 0; } std::shared_ptr<Foo> getSharedPtr() { return shared_from_this(); } }; int main() { std::shared_ptr<Foo> sp(new Foo); std::shared_ptr<Foo> spT = sp->getSharedPtr(); return 0; }
shared_ptr不是线程安全的,它的的线程安全级别和内置类型、容器、string 一样,即:
l 多个线程对不同的shared_ptr写入是线程安全的,即使这些shared_ptr指向同一原始指针。
l 同一个 shared_ptr 可被多个线程同时读取。
l 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。
如下所示:
shared_ptr<Foo> globalPtr; Mutex mutex; void read() { shared_ptr<Foo> ptr; { MutexLock lock(mutex); //RAII Mutex ptr = globalPtr; // read globalPtr } // use ptr since here doit(ptr); } void write() { shared_ptr<Foo> newptr(new Foo); { MutexLock lock(mutex); //RAII Mutex globalPtr = newptr; // write to globalPtr } // use newptr since here doit(newptr); }
应该避免使用临时的shared_ptr对象,对此,boost上的说明是:假设有下面的代码,ok函数是正确的做法,而bad函数有可能会导致内存泄露,因为f函数参数的执行顺序可能是先new int(2),再执行g(),然后再执行shared_ptr<int>(),假设在g()方法中产生了异常,将不会再执行shared_ptr<int>(),new int(2)的内存泄露。
void f(shared_ptr<int>, int); int g(); void ok() { shared_ptr<int> p(new int(2)); f(p, g()); } void bad() { f(shared_ptr<int>(new int(2)), g()); }
③、工厂函数make_shared
当shared_ptr的构造参数是一个new操作符的时候,虽然我们不用手动调用delete来释放它,可这导致了代码中的某种不对称性,所以应该使用工厂模式来解决:头文件"boost/make_shared.hpp"中提供了一个自由工厂函数make_shared<T>()来消除显示的new操作,它可以返回一个shared_ptr<T>对象,使用示例:
#include "boost/smart_ptr.hpp" #include "boost/make_shared.hpp" #include <vector> int main() { boost::shared_ptr<string> sp = boost::make_shared<string>("make_shared"); boost::shared_ptr<std::vector<int>> spv = boost::make_shared<std::vector<int>>(10, 2); assert(spv->size() == 10); return 0; }
C++11中也提供了std::make_shared<T>()来使用。
④、删除器
shared_ptr默认使用delete释放它指向的对象,我们可以通过使用其另一种构造方法来指定其他的释放操作。
shared_ptr有一种特殊形式的构造函数:shared_ptr(T* p, D d); 这里边的d就是删除器,它可以是一个函数对象或函数指针。删除器用来指定shared_ptr在析构的时候即离开作用域的时候不是执行释放内存的操作,而是执行d函数。函数get_deleter()可以获得删除器指针。
删除器使用示例1:
std::unique_ptr<int> u5 (new int, std::default_delete<int>());
删除器使用示例2:
class CSock { ... }; CSock* open_sock() { CSock* s = new CSock; ...//do some open job return p; } void close_sock(CSock* s) { ...//do some close job delete s; } boost::shared_ptr(CSock*)(open_sock(), close_sock);
删除器使用示例3:
struct Foo { Foo() { std::cout << "Foo... "; } ~Foo() { std::cout << "~Foo... "; } }; struct D { void operator()(Foo* p) const { std::cout << "Call delete from function object... "; delete p; } }; std::shared_ptr<Foo> sh4(new Foo, D()); std::shared_ptr<Foo> sh5(new Foo, [](auto p) { std::cout << "Call delete from lambda... "; delete p; });
删除器使用示例4:
//当shared_ptr离开作用域的时候,自动调用fclose()关闭文件。 shared_ptr<FILE> fp(fopen("./1.txt", "r"), fclose);
⑤、shared_array
使用shared_array来指向使用new[]开辟的数组,它同样使用引用计数机制。
⑥、weak_ptr
我们一般使用一个shared_ptr或weak_ptr来初始化weak_ptr,当使用一个shared_ptr对象来构造weak_ptr的时候不会引起shared_ptr引用计数的增加,即weak_ptr不能控制对象的生命期。
weak_ptr主要用来获悉对象是存在的还是已经被释放了,通过成员函数lock()。lock()可以从被观测的shared_ptr获得一个shared_ptr对象,如果对象存在的话,lock()会导致shared_ptr的引用计数加1,如果对象已经被释放了则lock()返回空的shared_ptr(以默认构造函数构造的shared_ptr,其use_count()为0)。lock()是线程安全的。weak_ptr的expired()方法可以用来判断当前weak_ptr对象是否是空的,当weak_ptr没有观测对象(没有用shared_ptr进行初始化)或者观测对象已经被释放expired()返回true。weak_ptr也有use_count()方法,它获得的是被观测对象(shared_ptr)的引用计数。
boost::weak_ptr<int> wpEmpty; bool b = wpEmpty.expired(); //true boost::shared_ptr<int> sp(new int(10)); boost::weak_ptr<int> wp(sp); b = wp.expired(); //false boost::shared_ptr<int> sp2 = wp.lock(); if (sp2) { assert(wp.use_count() == 2); } sp.reset(); sp2.reset(); b = wp.expired(); //true
当两个shared_ptr互相包含的时候,会导致双方无法释放的问题,比如下面这种情况,当func()返回的时候两个对象都得不到释放,解决方法是使用weak_ptr来代替shared_ptr。
class Child; class Parent { public: ~Parent() { cout << "Parent destruct" << endl; } shared_ptr<Child> m_spChild; }; class Child { public: ~Child() { cout << "Child destruct" << endl; } shared_ptr<Parent> m_spParent; }; void Func() { shared_ptr<Parent> spParent(new Parent); shared_ptr<Child> spChild(new Child); spParent->m_spChild = spChild; spChild->m_spParent = spParent; } int main() { Func(); getchar(); return 0; }
3、unique_ptr
unique_ptr与shared_ptr不同的是只能有一个unique_ptr指向一个对象,因此unique_ptr不支持普通的拷贝或赋值操作(使用工厂函数make_unique<>进行 = 初始化除外)。一般情况下不推荐使用unique_ptr,因为在一些特定的复杂情况下unique_ptr的特性会导致内存不能复制而编译出错:
std::unique_ptr<string> p1(new string("test")); std::unique_ptr<string> p2 = std::make_unique<string>("hello"); p2 = std::make_unique<string>("abc"); p2 = p1; //error std::unique_ptr<string> p3(p1); //error std::unique_ptr<string> p4 = p1; //error
成员方法get()可以获得当前管理对象的指针,release()或reset()将指针的所有权从一个unique_ptr转移到另一个unique_ptr。release()会返回当前保存的指针,而且它仅仅是切断与当前管理对象的关系,并不会释放该对象资源。reset()接收一个新的指针,并将原来管理的对象释放。不能拷贝unique_ptr的规则有一个例外,我们可以拷贝一个将要被销毁的unique_ptr,最常见的例子是从函数返回一个unique_ptr:
std::unique_ptr<string> sp1; sp1.reset(new string); //使用reset初始化 std::unique_ptr<string> sp2; sp2 = std::unique_ptr<string>(new string); //使用临时的unique_ptr赋值 std::unique_ptr<int> clone(int num) { //返回临时unique_ptr return std::unique_ptr<int>(new int(num)); } std::unique_ptr<int> clone(int num) { std::unique_ptr<int> ret(new int(num)); return ret; //返回局部unique_ptr }
当我们使用容器保存unique_ptr的时候应该使用临时的unique_ptr或std::move():
std::vector<std::unique_ptr<int>> vc; //使用临时的unique_ptr vc.push_back(std::make_unique<int>(100)); //使用std::move() std::unique_ptr<int> up = std::make_unique<int>(100); vc.push_back(std::move(up));
类似shared_ptr,unique_ptr也可以指定删除器,但它与管理删除器的方式与shared_ptr有所不同。