Chrome代码中大量运用了智能指针来管理对象的指针,解决对象生命期的问题。这篇文章尝试着理解Chrome中定义的几个智能指针类。
1. scoped_ptr/scoped_array/scopred_array_malloc
以scopred开头的智能指针类定义在/src/base/scoped_ptr.h文件中。它们有着很明确的设计目标,对new/new[]/malloc出来的对象指针进行简单的包装,来管理对象的内存分配和释放。比如scoped_ptr类对new/delete进行了简单的包装,提供类似于std::auto_ptr类似的接口,但是却摒弃std::auto_ptr设计中令人诟病的拷贝复制时被管理的对象转移的问题。简单的禁用拷贝和复制函数就可以达到此效果。所以它们的使用场合就非常明确和简单有限:
// { // scoped_ptr<Foo> foo; // No pointer managed. // foo.reset(new Foo("wee")); // Now a pointer is managed. // foo.reset(new Foo("wee2")); // Foo("wee") was destroyed. // foo.reset(new Foo("wee3")); // Foo("wee2") was destroyed. // foo->Method(); // Foo::Method() called. // foo.get()->Method(); // Foo::Method() called. // SomeFunc(foo.Release()); // SomeFunc takes owernship, foo no longer // // manages a pointer. // foo.reset(new Foo("wee4")); // foo manages a pointer again. // foo.reset(); // Foo("wee4") destroyed, foo no longer // // manages a pointer. // } // foo wasn't managing a pointer, so nothing was destroyed.同时它们也禁止了两个智能指针之间的比,较,因为它们都不允许一个对象同时被2个智能指针同时管理:
private: // Forbid comparison of scoped_ptr types. If C2 != C, it totally doesn't // make sense, and if C2 == C, it still doesn't make sense because you should // never have the same object owned by two different scoped_ptrs. template <class C2> bool operator==(scoped_ptr<C2> const& p2) const; template <class C2> bool operator!=(scoped_ptr<C2> const& p2) const;有一点需要注意的是,类中的很多函数用了编译器的检查机制:
~scoped_ptr() { enum { type_must_be_complete = sizeof(C) }; delete ptr_; }所以,编译这些模板类时,必须知道管理对象的定义。也就是说被管理对象的定义须在这些模板类定义之前给出。
2. scoped_refptr/RefCounted<T>/RefCountedThreadSafe<T>
除了上面介绍的简单的智能指针之外,还有一个广泛使用的智能指针是针对智能指针的,类似于boost::shared_ptr类,它就是scoped_refptr。它管理的对象需继承于模板类RefCounted<T>,T就是自己本身。RefCounted<T>模板了提供了AddRef和Release函数(yy一下,为什么不叫IncRef和DecRef,意义不更明确吗?),同时还维护一个INT类型的引用计数。RefCountedThreadSafe<T>是线程安全的RefCounted版本,原因在于引用计数不是采用INT类型,而是一个AtomicRefCount类型(原子的)。
我们重点来关注下scoped_refptr模板类:
template <class T> class scoped_refptr { public: scoped_refptr() : ptr_(NULL) { } scoped_refptr(T* p) : ptr_(p) { if (ptr_) ptr_->AddRef(); } scoped_refptr(const scoped_refptr<T>& r) : ptr_(r.ptr_) { if (ptr_) ptr_->AddRef(); } ~scoped_refptr() { if (ptr_) ptr_->Release(); } T* get() const { return ptr_; } operator T*() const { return ptr_; } T* operator->() const { return ptr_; } scoped_refptr<T>& operator=(T* p) { // AddRef first so that self assignment should work if (p) p->AddRef(); if (ptr_ ) ptr_ ->Release(); ptr_ = p; return *this; } scoped_refptr<T>& operator=(const scoped_refptr<T>& r) { return *this = r.ptr_; } void swap(T** pp) { T* p = ptr_; ptr_ = *pp; *pp = p; } void swap(scoped_refptr<T>& r) { swap(&r.ptr_); } protected: T* ptr_; };
boost::shared_ptr自己管理引用计数,每当它的拷贝构造和赋值运算符被调用时,这个引用计数加1。而scoped_refptr不管理引用计数,由对象自己管理。这样就带来下面不同的用法:
boost::shared_ptr的用例(不正确的):
int* pa = new int(3); { boost::shared_ptr<int> psa(pa); ... boost::shared_ptr<int> psb(pa); }psa/psb被销毁时都会尝试删除pa,结果导致double-delete问题出现。
如果换成scoped_ptr,那么就不会出现上述的问题。
关于智能指针的拥有权策略技术,共有deep copy, reference counting, reference linking和destructive copy。<Modern C++ Design>书籍第7章第5节详细讲到了这几种技术的优缺点,而且Loki库也提供这几种策略技术的实作版本。Chrome里面的智能指针和boost::shared_ptr都是采用reference counting技术。