第五章,技术 28-Smart Pointers(智能指针) 前置申明 std::auto_ptr已经过时,在C++11中标记为弃用,C++17中已经被完全移除;
理解该条款其核心思想: 1、RALL(资源获取即初始化原则) 2、通过对象管理资源,避免内存泄漏 3、控制对象所有权语义(独占vs共享)
需要更新的技术细节: 1、不再使用std::auto_ptr,改用std::unique_ptr(独占所有权) 2、优先使用std::make_unique和std::make_shared而非裸new 3、现代智能指针已标准化,通常不需要自己编写智能指针类
注意 现代C++代码编写应该优先使用智能指针而非裸指针
std::unique_ptr 基本用法
#include <memory> std::unique_ptr<int > p1 (new int (42 )) ;auto p2 = std::make_unique <int >(42 );auto p3 = std::make_unique <std::string>("hello" );
关键特性
std::unique_ptr<int > a = std::make_unique <int >(10 ); std::unique_ptr<int > b = a; std::unique_ptr<int > c (a) ; std::unique_ptr<int > d = std::move (a); std::unique_ptr<int []> arr (new int [100 ]) ; std::unique_ptr<FILE, decltype (&fclose) > file ( fopen("test.txt" , "r" ), &fclose ) ;
作为函数参数与返回值
void useButNotOwn (std::unique_ptr<Widget>& widget) { widget->doSomething (); } void takeOwnership (std::unique_ptr<Widget> widget) { } auto w = std::make_unique <Widget>();useButNotOwn (w); takeOwnership (std::move (w)); std::unique_ptr<Widget> createWidget () { return std::make_unique <Widget>(); } auto widget = createWidget ();
std::shared_ptr shared_ptr 类描述使用引用计数来管理资源的对象。 shared_ptr 对象有效保留一个指向其拥有的资源的指针或保留一个 null 指针。 资源可由多个 shared_ptr 对象拥有;当拥有特定资源的最后一个 shared_ptr 对象被销毁后,资源将释放。
引用计数在独立控制块内(堆),所有shared_ptr共享,即所有shared_ptr都指向该控制块
#include <memory> std::shared_ptr<int > p1 (new int (42 )) ;auto p2 = std::make_shared <int >(42 );auto p3 = std::make_shared<std::vector<int >>(10 , 100 ); std::shared_ptr<int > p4 = p2; p4 = p3;
关键特性
auto p = std::make_shared <int >(10 );{ auto q = p; auto r = p; } std::cout << p.use_count (); std::shared_ptr<FILE> file ( fopen("test.txt" , "r" ), &fclose ) ;class Widget : public std::enable_shared_from_this<Widget> {public : std::shared_ptr<Widget> getShared () { return shared_from_this (); } }; auto w = std::make_shared <Widget>();auto w2 = w->getShared ();
常见陷阱
int * raw = new int (10 ); std::shared_ptr<int > a (raw) ;std::shared_ptr<int > b (raw) ; auto a = std::make_shared <int >(10 );auto b = a;
std::weak_ptr 描述了一个指向由一个或多个 shared_ptr 对象管理的资源的对象。 指向某个资源的 weak_ptr 对象不会影响该资源的引用计数。 当最后一个管理该资源 shared_ptr 的对象被销毁时,则即使存在指向该资源的 weak_ptr 对象,该资源也将被释放。 此行为对于避免数据结构中的循环至关重要。
需要使用该资源的代码可通过一个 shared_ptr 对象来执行该操作,该对象通过调用成员函数 lock 创建并拥有该资源。 weak_ptr 对象在其所指向的资源被释放时已过期,因为所有拥有该资源的 shared_ptr 对象已被销毁。 调用已过期的 weak_ptr 对象上的 lock 将创建一个空 shared_ptr 对象
#include <memory> auto sp = std::make_shared <int >(42 );std::weak_ptr<int > wp = sp; if (auto locked = wp.lock ()) { std::cout << *locked; } else { std::cout << "对象已销毁" ; } if (wp.expired ()) { std::cout << "对象已不存在" ; }
解决循环引用问题
两个对象互相持有对方的shared_ptr,导致引用计数永远不为0,无法销毁。
#include <iostream> #include <memory> class B ; class A {public : std::shared_ptr<B> b_ptr; ~A () { std::cout << "A 销毁\n" ; } }; class B {public : std::shared_ptr<A> a_ptr; ~B () { std::cout << "B 销毁\n" ; } }; int main () { auto a = std::make_shared <A>(); auto b = std::make_shared <B>(); a->b_ptr = b; b->a_ptr = a; std::cout << "离开作用域...\n" ; }
实用技法: 内部提供函数self获取指向本身的智能指针shared_ptr 将当前对象作为shared_ptr传递时安全保证引用计数正常,不会重复释放
class Foo : public std::enable_shardered_from_this<Foo>{public : Foo (){...} ~Foo (){...} ... shared_ptr<Foo> self () { return shared_from_this (); } }; Foo *f = new Foo; auto f00 = f->self ();std::shared_ptr<Foo> f = std::make_shared <Foo>(); auto f01 = f->self ();
再提取该条款中原著的一些有用的东西 1、利用对象的构造析构来开始和结束运转纪录
template <class T >class LogEntry {public : LogEntry (const T& objectToBeModified); ~LogEntry (); ... }; void editTuple (...) { LogEntry<...> entry (*pt) ; ... }
2、C++的对象切片(切割问题)
Derived d; Base b = d; template <class T >T& SmartPtr<T>::operator *() const { ... return *pointee;}
3、abort与exit区别
4、定义隐式转换,实现类对象可判断是否为null 基于条款5,我们知道隐式转换会带来一些问题,但用于判断的话,我觉得正是隐式判断的用武之地
template <class T >class SmartPtr {public : ... operator void *(); ... }; SmartPtr<TreeNode> ptn; ... if (ptn == 0 ) ... if (ptn) ... if (!ptn) ...
实际上,现在的智能指针也是提供专门的函数来获取裸指针,而不是使用隐式转换: .get()借用 .release()接管
{ auto sp = std::make_shared <int >(42 );int * r1 = sp.get (); } { auto up = std::make_unique <int >(42 );int * r2 = up.release (); } { auto up = std::make_unique <int >(42 );int * r2 = up.get (); delete r2; }
5、令人眼前一亮的技法,通过成员模板函数实现【模板类】的【模板类型】所在继承体系【向上转型】
template <class T >class SmartPtr { T* pointee; public : template <class newType > operator SmartPtr <newType>() { return SmartPtr <newType>(pointee); } }; class Base {};class Derived : public Base {};SmartPtr<Derived> dptr (new Derived) ;SmartPtr<Base> bptr = dptr; void function (const SmartPtr<Base>& base) { ... } function (dptr); { SmartPtr<Derived>::operator SmartPtr <Base>(){ return SmartPtr <Base>(pointee); } } class GrandDerived : public Derived {};void function (const SmartPtr<Derived>& base) { ... } SmartPtr<GrandDerived> gdptr (new GrandDerived) ;function (gdptr);
如今的智能指针也支持继承体系的向上转型
#include <memory> class Base {};class Derived : public Base {};std::shared_ptr<Derived> dptr = std::make_shared <Derived>(); std::shared_ptr<Base> bptr = dptr; void func (const std::shared_ptr<Base>& base) { } func (dptr);
6、在类中使用union的设计
template <class T >class SmartPtrToConst { ... functions protected : union { const T* constPointee; T* pointee; }; }; template <class T >class SmartPtr : public SmartPtrToConst<T>{ ... functions };
auto_ptr被C++11废弃的原因 auto_ptr 被移除不是因为”独占所有权”的概念错误,而是因为在缺乏语言级移动语义(C++11之前)的时代,被迫用拷贝语法模拟移动语义,导致了严重的安全性和可用性问题。 unique_ptr 借助右值引用和移动语义,以显式、类型安全的方式实现了同样的独占所有权模型。
std::auto_ptr<int > p1 (new int (42 )) ;std::auto_ptr<int > p2 = p1; *p1;