boost-智能指针
使用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不能多次引用同一原始指针,也就是说一个指针只能赋值给一个shared_ptr,否则会产生多次释放错误:
CFoo* p = new CFoo; std::shared_ptr<CFoo> sp(p); std::shared_ptr<CFoo> sp2(sp); //正确 std::shared_ptr<CFoo> sp3(p); //错误,会产生释放问题
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需要注意两点:shared_from_this()方法不能在当前类的构造函数 / 析构函数中调用,因为此时对象还没有构造完成 / 已经处于析构状态,所以应该避免在其它线程中调用对象的shared_from_this,因为此时的对象可能在该线程中正在处于构造或析构中,除非能确保此时的对象已经被定义好且未被释放;如果当前对象由一个unique_ptr控制生命,那么对象内不能使用shared_from_this(),所以我们定义对象的时候应该使用shared_ptr:
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...\n"; } ~Foo() { std::cout << "~Foo...\n"; } }; struct D { void operator()(Foo* p) const { std::cout << "Call delete from function object...\n"; 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...\n"; 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; }
weak_ptr应该在第一个shared_ptr定义后就将第一个shared_ptr赋给weak_ptr,否则其检测的生存状态有问题,如下所示:
std::shared_ptr<int> sp1(new int); //std::weak_ptr<int> wp(sp1); //应该在这里就将sp赋给wp,然后下面的isFree是false std::shared_ptr<int> sp2(sp1); sp1.reset(); std::weak_ptr<int> wp(sp1); //在这里将sp赋给wp的话,下面的isFree是true bool isFree = wp.expired(); //true,不符合预期
如下为使用数据层请求数据和处理数据层返回的示例,如果数据层返回的通知中不需要对数据类的成员进行访问的话,也可以不使用shared_from_this(),参见如下所示的第二段代码:
class CData : public std::enable_shared_from_this<CData> //数据类 { public: ...... void request() { std::weak_ptr<CData> wp(shared_from_this()); //因为shared_from_this()不能在构造函数中使用,所以request()方法严禁在CData的构造方法中使用 std::function<void(const std::string& data)> funCallback = [this, wp](const std::string& data) { auto sp = wp.lock(); if (!sp) //本对象已被释放返回,未被释放的话lock会增加对象的一个引用,所以对象在本方法中至少存在一个引用计数,可以在本方法中放心的使用对象的数据成员 return; { std::lock_guard<std::mutex> _lock(m_mutex); //因为在数据层的线程中,所以需要加锁 m_strData = data; } MessageManager::callAsync([this, wp] { if (wp.expired()) //本对象已被释放则返回,对象未被释放则可以放心使用它,因为这里是在主线程而不是数据层的线程 return; if (nullptr != m_pListener) m_pListener->update(); }); }; DataLayer::requestData(funCallback); //调用数据层提供的请求数据方法 } std::string getData() { std::lock_guard<std::mutex> _lock(m_mutex); return m_strData; } class CListener { public: virtual ~CListener() {} virtual void update() = 0; }; void setListener(CListener* pListener) { m_pListener = pListener; } ...... private: CListener* m_pListener = nullptr; std::string m_strData; std::mutex m_mutex; ...... };
class CData //数据类 { public: CData() { m_spFreeFlag.reset(new int); } virtual ~CData() { m_spFreeFlag.reset(); ...... } void request() { std::weak_ptr<CData> wp(m_spFreeFlag); std::function<void(const std::string& data)> funCallback = [this, wp](const std::string& data) { if (wp.expired()) return; MessageManager::callAsync([this, wp] { if (wp.expired()) return; if (nullptr != m_pListener) m_pListener->update(); }); }; DataLayer::requestData(funCallback); } class CListener { public: virtual ~CListener() {} virtual void update() = 0; }; void setListener(CListener* pListener) { m_pListener = pListener; } ...... private: CListener* m_pListener = nullptr; std::shared_ptr<int> m_spFreeFlag; ...... };
有时候可能数据类里返回的数据比较多,我们希望接收了一部分数据就进行展示,代码如下所示:
class CData : public std::enable_shared_from_this<CData> //数据类 { public: ...... void request() { std::weak_ptr<CData> wp(shared_from_this()); std::function<void(const std::string& data)> funCallback = [this, wp](const std::string& data) { auto sp = wp.lock(); if (!sp) return; auto ary = toArray(data); for (int i = 0; i < ary.size(); ++i) { m_mutex.lock(); m_data.push_back(item); m_mutex.unlock(); if (i == 9) { //获取到了10条数据就更新界面 MessageManager::callAsync([this, wp] { if (wp.expired()) return; if (nullptr != m_pListener) m_pListener->update(); }); } } MessageManager::callAsync([this, wp] { if (wp.expired()) return; if (nullptr != m_pListener) m_pListener->update(); }); }; DataLayer::requestData(funCallback); } ...... };
上面代码其实会有个问题,那就是在第一个MessageManager::callAsync()之前如果我们关闭了主窗口的话,因为数据类中数据处理还在执行中,第一个MessageManager::callAsync()中的wp.expired()会返回false,然后会调用m_pListener->update()通知主窗口更新界面,但这个时候主窗口实际上已经关闭销毁了,所以就会出现问题。解决方法就是我们可以给数据类传入一个主窗口的weak_ptr,在MessageManager::callAsync()中通过这个weak_ptr判断主窗口是否已经关闭销毁。为了避免数据类中包含主窗口头文件,我们可以实现一个主窗口的父类,weak_ptr指向的是该父类对象,代码如下所示。
class CSharedObj : public std::enable_shared_from_this<CSharedObj> { public: CSharedObj() {} virtual ~CSharedObj() {} std::weak_ptr<CSharedObj> getWP() { std::weak_ptr<CSharedObj> wp(shared_from_this()); return wp; } }; class CMainWnd : public CSharedObj, public CData::CListener { public: CMainWnd() { m_spData.reset(new CData); auto mainWP = getWP(); m_spData->request(mainWP); } void update()override { auto data = m_spData->getData(); //更新窗口数据 ...... } ...... private: std::shared_ptr<CData> m_spData; ...... }; class CData : public std::enable_shared_from_this<CData> //数据类 { public: ...... void request(const std::weak_ptr<CYzyqZL>& wpMain) { std::weak_ptr<CData> wp(shared_from_this()); std::function<void(const std::string& data)> funCallback = [this, wp, wpMain](const std::string& data) { auto sp = wp.lock(); if (!sp) return; auto ary = toArray(data); for (int i = 0; i < ary.size(); ++i) { m_mutex.lock(); m_data.push_back(item); m_mutex.unlock(); if (i == 9) { //获取到了10条数据就更新界面 MessageManager::callAsync([this, wpMain] { if (wpMain.expired()) return; if (nullptr != m_pListener) m_pListener->update(); }); } } MessageManager::callAsync([this, wpMain] { if (wpMain.expired()) return; if (nullptr != m_pListener) m_pListener->update(); }); }; DataLayer::requestData(funCallback); } ...... };
上面的代码可以看到使用了两个weak_ptr,一个数据类的,一个主窗口的,那么可不可以只使用主窗口的一个weak_ptr呢?答案是否定的,因为我们使用数据类的weak_ptr对其进行lock()以防止主窗口关闭后对其进行销毁,如果改成使用主窗口的weak_ptr进行lock()的话,在数据处理的时候我们关闭窗口的话主窗口对象不会被销毁,然后数据类通知主窗口进行更新的话主窗口会调用相关的方法进行界面更新,而此时实际上该窗口已经被关闭,再进行repait()等界面更新操作的话就会出错。
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()也可以获得当前管理对象指针,但它会切断unique_ptr与当前管理对象的关系(不会释放该对象),也就是说调用release后unique_ptr相当于是一个野指针了。reset()接收一个新的指针,它会将unique_ptr原来管理的对象释放。不能拷贝unique_ptr的规则有一个例外,我们可以拷贝一个将要被销毁的unique_ptr,最常见的例子是从函数返回一个unique_ptr:
std::unique_ptr<std::string> p1(new std::string("test")); std::unique_ptr<std::string> p2(p1.release()); std::cout << *p1; //error
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有所不同。