资源管理在软件开发中历来都是老大难问题,堆上内存的管理尤其麻烦。现代编程语言引入了垃圾回收机制,如 Java,Python,Go 等,在内存管理方面为开发者提供了极大的便利,开发者面对内存泄漏问题的麻烦大大减少。
现代 C++ 的一大进步就是在资源管理方面。开发者可以很方便地将资源的生命周期管理,如堆上内存,文件描述符,Socket 句柄等,转化为对象的生命周期管理,并借助于 C++ 的对象析够机制,使得资源的生命周期管理大为简化。
相对于其它现代编程语言,C++ 提供了操作符重载这种强大的工具,将普通的操作符与函数统一起来,使得资源管理更为方便。C++ 的对象析够机制,总是能够确保一个对象在超出它的作用域之后被释放掉,如函数的局部对象在函数返回后必然被析够,堆上的对象可以确保在调用它的 delete
之后被释放掉,这相对于垃圾回收机制更为可靠。本文主要讨论堆上内存的管理。
首先来看一下堆上内存管理的难点。堆上对象的分配,通常不是什么太大的问题,分配的点总是比较容易分析,比较麻烦的是对象的释放。如果一个对象只被另外一个对象使用,则对象的生命周期管理也比较容易。当一个堆上分配的对象,同时为多个其它对象,特别是同时为多个不同线程访问时,释放时机的选择就比较棘手了。
一个对象持有指向某个堆上对象的指针时,它根本无法判断这个对象是否已经被释放。一个堆上对象被某个对象释放,其它对象再去访问它时,比较好的情况是,程序立即由于 SIGSEGV
而崩溃,更糟糕的情况则是,程序在一段时间内继续运行,最后在莫名其妙的位置发生崩溃或者死锁,这还会给问题诊断制造麻烦。
如果访问某个堆上对象的所有对象都没有去释放它,则将会造成内存泄漏。内存资源将随着程序运行时间的流逝,而逐渐被消耗殆尽。
现代 C++ 通过智能指针管理堆上内存。智能指针并不是真正的指针,而是一个类对象,但它的行为基本上就像普通的指针那样。通常我们对指针所做的操作主要有:通过解引用操作符 ->
访问指针指向的对象的成员,通过 *
操作符获得指针指向的对象的引用,复制,逻辑判断如相等性判断、不等性判断等,指针比较等。智能指针可以提供所有这些操作,但不像普通指针那样由编程语言提供,而是借助于操作符重载,实现响应的操作符函数。
在智能指针的使用上,通常我们先在堆上分配一个对象,将指向对象的指针给智能指针对象,智能指针对象在释放时,根据需要释放我们在堆上分配的对象。以此堆上资源生命周期管理问题被转换为了智能指针对象的生命周期管理问题。
当然智能指针也有不好的地方,或者说存在风险的地方。如果两个用智能指针管理的对象,它们相互之间通过智能指针引用对方,将难免会造成内存泄漏。要避免这种问题,需要仔细地考虑对象间的关系,并适当使用 weak_ptr
。智能指针的使用也还是需要多加小心。
本文讨论智能指针的使用和实现。C++ 中的智能指针,就对待所管理指针的方式而言,可分为两大类。一是如 C++ STL 中的 std::unique_ptr
,boost 的 boost::scoped_ptr
和 boost::scoped_array
这样,在智能指针对象销毁时,直接释放所管理的堆上对象的。二是如 C++ STL 中的 std::shared_ptr
,Poco 库的 Poco::AutoPtr
, Android framework 中的 sp
这样,基于引用计数实现,在智能指针对象复制时增加引用计数,在智能指针对象释放时减小引用计数,只有当引用计数减小为 0 时,才释放对象的。
基于引用计数实现的智能指针,依据其侵入性的不同,又分为两类:一是具有一定的侵入性,要求被智能指针管理的对象的类继承某个特定类的,如 Poco 库里的 Poco::AutoPtr
和 android 中的 sp
;二是对被管理的对象没有任何要求的,如 C++ STL 中的 std::shared_ptr
。
本文分析几种智能指针的使用与实现:boost 的 boost::scoped_ptr
,Poco 库的 Poco::AutoPtr
, Android framework 中的 sp
,及 C++ STL 的 std::shared_ptr
。其中 boost 的 boost::scoped_ptr
基于 boost 1.58.0 的代码来分析;Poco 库的 Poco::AutoPtr
基于 1.6.0;Android 的 sp
基于 android 7.1.1 r22 ;C++ STL 的 std::shared_ptr
则基于我的 Ubuntu Linux GCC 5.4.0 带的 STL 实现。
boost::scoped_ptr 的使用及实现
先来看一下 boost::scoped_ptr
用法:
#include <iostream>
#include <boost/smart_ptr.hpp>
class TestObj {
public:
TestObj();
virtual ~TestObj();
int i;
};
TestObj::TestObj() : i (32412) {
std::cout << "Create TestObj" << this << std::endl;
}
TestObj::~TestObj() {
std::cout << "Release TestObj" << this << std::endl;
}
int main() {
boost::scoped_ptr<TestObj> objPtr(new TestObj);
std::cout << "TestObj i = " << objPtr->i << std::endl;
std::cout << "TestObj i = " << (*objPtr).i << std::endl;
return 0;
}
在创建 boost::scoped_ptr
对象的时候,把需要由它管理的堆上对象的指针传给构造函数,在 boost::scoped_ptr
对象析够的时候,它所管理的指针会被自动 delete
。我们可以通过 boost::scoped_ptr
对象像使用普通指针那样访问被管理的对象的成员,获得对象的引用。boost::scoped_ptr
是一种没有任何侵入性的智能指针,它可以管理任何类型的堆上对象的指针。
boost::scoped_ptr
是一个模板类,其代码位于
boost/smart_ptr/scoped_ptr.hpp
, 如下面这样:
namespace boost
{
// Debug hooks
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
void sp_scalar_constructor_hook(void * p);
void sp_scalar_destructor_hook(void * p);
#endif
// scoped_ptr mimics a built-in pointer except that it guarantees deletion
// of the object pointed to, either on destruction of the scoped_ptr or via
// an explicit reset(). scoped_ptr is a simple solution for simple needs;
// use shared_ptr or std::auto_ptr if your needs are more complex.
template<class T> class scoped_ptr // noncopyable
{
private:
T * px;
scoped_ptr(scoped_ptr const &);
scoped_ptr & operator=(scoped_ptr const &);
typedef scoped_ptr<T> this_type;
void operator==( scoped_ptr const& ) const;
void operator!=( scoped_ptr const& ) const;
public:
typedef T element_type;
explicit scoped_ptr( T * p = 0 ): px( p ) // never throws
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_constructor_hook( px );
#endif
}
#ifndef BOOST_NO_AUTO_PTR
explicit scoped_ptr( std::auto_ptr<T> p ) BOOST_NOEXCEPT : px( p.release() )
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_constructor_hook( px );
#endif
}
#endif
~scoped_ptr() // never throws
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_destructor_hook( px );
#endif
boost::checked_delete( px );
}
void reset(T * p = 0) // never throws
{
BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors
this_type(p).swap(*this);
}
T & operator*() const // never throws
{
BOOST_ASSERT( px != 0 );
return *px;
}
T * operator->() const // never throws
{
BOOST_ASSERT( px != 0 );
return px;
}
T * get() const BOOST_NOEXCEPT
{
return px;
}
// implicit conversion to "bool"
#include <boost/smart_ptr/detail/operator_bool.hpp>
void swap(scoped_ptr & b) BOOST_NOEXCEPT
{
T * tmp = b.px;
b.px = px;
px = tmp;
}
};
#if !defined( BOOST_NO_CXX11_NULLPTR )
template<class T> inline bool operator==( scoped_ptr<T> const & p, boost::detail::sp_nullptr_t ) BOOST_NOEXCEPT
{
return p.get() == 0;
}
template<class T> inline bool operator==( boost::detail::sp_nullptr_t, scoped_ptr<T> const & p ) BOOST_NOEXCEPT
{
return p.get() == 0;
}
template<class T> inline bool operator!=( scoped_ptr<T> const & p, boost::detail::sp_nullptr_t ) BOOST_NOEXCEPT
{
return p.get() != 0;
}
template<class T> inline bool operator!=( boost::detail::sp_nullptr_t, scoped_ptr<T> const & p ) BOOST_NOEXCEPT
{
return p.get() != 0;
}
#endif
template<class T> inline void swap(scoped_ptr<T> & a, scoped_ptr<T> & b) BOOST_NOEXCEPT
{
a.swap(b);
}
// get_pointer(p) is a generic way to say p.get()
template<class T> inline T * get_pointer(scoped_ptr<T> const & p) BOOST_NOEXCEPT
{
return p.get();
}
} // namespace boost
Poco 库 Poco::AutoPtr 的用法及实现
先来看一下用法:
class TestObj: public RefCountedObject {
public:
TestObj();
virtual ~TestObj();
};
TestObj::TestObj() {
}
TestObj::~TestObj() {
}
typedef Poco::AutoPtr<TestObj> TestObjPtr;
typedef list<TestObjPtr> TestObjList;
void test_ap() {
Poco::AutoPtr<TestObj> ptr = new TestObj;
cout << "Initial refcount = " << ptr->referenceCount() << endl;
Poco::AutoPtr<TestObj> ptr2 = ptr;
cout << "Copy one time refcount = " << ptr->referenceCount() << endl;
list<Poco::AutoPtr<TestObj> > testObjList;
testObjList.push_back(ptr);
cout << "Push into a list refcount = " << ptr->referenceCount() << endl;
testObjList.remove(ptr);
cout << "Remove from a list refcount = " << ptr->referenceCount() << endl;
ptr2 = new TestObj;
cout << "refcount = " << ptr->referenceCount() << endl;
TestObjPtr ptr3;
ptr3 = ptr2;
cout << "ptr2 refcount = " << ptr2->referenceCount() << endl;
ptr3 = new TestObj;
cout << "ptr2 refcount = " << ptr2->referenceCount() << endl;
ptr3 = ptr2;
cout << "ptr2 refcount = " << ptr2->referenceCount() << endl;
}
如下是上面这段code执行的结果:
Initial refcount = 1
Copy one time refcount = 2
Push into a list refcount = 3
Remove from a list refcount = 2
refcount = 1
ptr2 refcount = 2
ptr2 refcount = 1
ptr2 refcount = 2
可以看到对象的引用计数随着流程的执行而改变,AutoPtr对象每复制一次,其管理的对象的引用计数就加一,而AutoPtr对象每释放一次,其管理的对象的引用计数就减一。
接着来具体说明一下要如何使用AutoPtr。首先,由智能指针来管理其对象释放的class需要继承Poco库的RefCountedObject。AutoPtr是一种基于引用计数的堆对象管理机制,因而,总要有个地方来存放一个对象的引用次数。AutoPtr管理的对象,用于记录引用计数的变量存放在该对象中,但那个变量继承自RefCountedObject(RefCountedObject提供的当然不止是这样的一个变量)。
一定要继承RefCountedObject才能使用Poco的AutoPtr吗?答案是不一定。但要由AutoPtr管理其对象的类,一定要符合模板AutoPtr所要求的隐式接口。具体点说,就是要提供RefCountedObject提供的接口,就像网上有些地方看到的例子,让那些类自己定义那些函数。当然接口的行为也要与RefCountedObject基本一致,比如,增加引用计数,减少引用计数,在引用计数减小为0时释放对象,否则的话,AutoPtr的作用也不能真正展现。
然后就可以RAII(资源获取即初始化),使用AutoPtr来管理new的对象了。我们可以放心的像使用Java里面的对象一样使用AutoPtr,而不用担心会有memory leak。
接着我们来看Poco::AutoPtr这套机制的实现。主要就是class RefCountedObject和模板AutoPtr的定义。先来看RefCountedObject的定义与实现:
#include "Poco/Foundation.h"
#include "Poco/AtomicCounter.h"
namespace Poco {
class Foundation_API RefCountedObject
/// A base class for objects that employ
/// reference counting based garbage collection.
///
/// Reference-counted objects inhibit construction
/// by copying and assignment.
{
public:
RefCountedObject();
/// Creates the RefCountedObject.
/// The initial reference count is one.
void duplicate() const;
/// Increments the object's reference count.
void release() const throw ();
/// Decrements the object's reference count
/// and deletes the object if the count
/// reaches zero.
int referenceCount() const;
/// Returns the reference count.
protected:
virtual ~RefCountedObject();
/// Destroys the RefCountedObject.
private:
RefCountedObject(const RefCountedObject&);
RefCountedObject& operator =(const RefCountedObject&);
mutable AtomicCounter _counter;
};
//
// inlines
//
inline int RefCountedObject::referenceCount() const {
return _counter.value();
}
inline void RefCountedObject::duplicate() const {
++_counter;
}
inline void RefCountedObject::release() const throw () {
try {
if (--_counter == 0)
delete this;
} catch (...) {
poco_unexpected();
}
}
} // namespace Poco
下面是这个class的实现文件:
RefCountedObject::RefCountedObject(): _counter(1)
{
}
RefCountedObject::~RefCountedObject()
{
}
这个类只有一个原子类型AtomicCounter的成员变量_counter,用来记录对象被引用的次数。可以看到它是被声明为mutable的,也就是说,即使是对于const的对象,在const方法中,也可以修改这个变量的值。
这个类提供了三个操作duplicate()、release()和referenceCount(),用来修改或读取_counter的值。这三个操作全都被声明为const,配合声明为mutable的_counter,使得AutoPtr这套机制,即使是对于new的const class也一样可用。duplicate()操作中,会增加引用计数,release()操作中会减少引用计数,referenceCount()操作则会将对象当前被引用的次数返回给调用者。在release()操作中,如果引用技术减到了0,当前对象会被delete掉。这些操作究竟会在什么地方调用到呢?答案是AutoPtr,后面看到模板AutoPtr的定义就能明白了。
这个class还采用了一些方法来阻止对于它的继承体系中对象的复制行为。可以看到,它声明了private的copy构造函数和赋值操作符,但两个函数都没有被定义。private声明可以在编译期阻止那些对于这个class的private成员没有访问权限的部分复制对象,而有意的不定义这两个函数,则可以在链接期阻止对这个class的private成员有访问权限的部分,比如一些friend class,friend方法,或内部的一些函数复制对象。
接着来看AutoPtr模板的定义:
template<class C>
class AutoPtr
/// AutoPtr is a "smart" pointer for classes implementing
/// reference counting based garbage collection.
/// To be usable with the AutoPtr template, a class must
/// implement the following behaviour:
/// A class must maintain a reference count.
/// The constructors of the object initialize the reference
/// count to one.
/// The class must implement a public duplicate() method:
/// void duplicate();
/// that increments the reference count by one.
/// The class must implement a public release() method:
/// void release()
/// that decrements the reference count by one, and,
/// if the reference count reaches zero, deletes the
/// object.
///
/// AutoPtr works in the following way:
/// If an AutoPtr is assigned an ordinary pointer to
/// an object (via the constructor or the assignment operator),
/// it takes ownership of the object and the object's reference
/// count remains unchanged.
/// If the AutoPtr is assigned another AutoPtr, the
/// object's reference count is incremented by one by
/// calling duplicate() on its object.
/// The destructor of AutoPtr calls release() on its
/// object.
/// AutoPtr supports dereferencing with both the ->
/// and the * operator. An attempt to dereference a null
/// AutoPtr results in a NullPointerException being thrown.
/// AutoPtr also implements all relational operators.
/// Note that AutoPtr allows casting of its encapsulated data types.
{
public:
AutoPtr() :
_ptr(0) {
}
AutoPtr(C* ptr) :
_ptr(ptr) {
}
AutoPtr(C* ptr, bool shared) :
_ptr(ptr) {
if (shared && _ptr)
_ptr->duplicate();
}
AutoPtr(const AutoPtr& ptr) :
_ptr(ptr._ptr) {
if (_ptr)
_ptr->duplicate();
}
template<class Other>
AutoPtr(const AutoPtr<Other>& ptr) :
_ptr(const_cast<Other*>(ptr.get())) {
if (_ptr)
_ptr->duplicate();
}
~AutoPtr() {
if (_ptr)
_ptr->release();
}
AutoPtr& assign(C* ptr) {
if (_ptr != ptr) {
if (_ptr)
_ptr->release();
_ptr = ptr;
}
return *this;
}
AutoPtr& assign(C* ptr, bool shared) {
if (_ptr != ptr) {
if (_ptr)
_ptr->release();
_ptr = ptr;
if (shared && _ptr)
_ptr->duplicate();
}
return *this;
}
AutoPtr& assign(const AutoPtr& ptr) {
if (&ptr != this) {
if (_ptr)
_ptr->release();
_ptr = ptr._ptr;
if (_ptr)
_ptr->duplicate();
}
return *this;
}
template<class Other>
AutoPtr& assign(const AutoPtr<Other>& ptr) {
if (ptr.get() != _ptr) {
if (_ptr)
_ptr->release();
_ptr = const_cast<Other*>(ptr.get());
if (_ptr)
_ptr->duplicate();
}
return *this;
}
AutoPtr& operator =(C* ptr) {
return assign(ptr);
}
AutoPtr& operator =(const AutoPtr& ptr) {
return assign(ptr);
}
template<class Other>
AutoPtr& operator =(const AutoPtr<Other>& ptr) {
return assign<Other>(ptr);
}
void swap(AutoPtr& ptr) {
std::swap(_ptr, ptr._ptr);
}
template<class Other>
AutoPtr<Other> cast() const
/// Casts the AutoPtr via a dynamic cast to the given type.
/// Returns an AutoPtr containing NULL if the cast fails.
/// Example: (assume class Sub: public Super)
/// AutoPtr<Super> super(new Sub());
/// AutoPtr<Sub> sub = super.cast<Sub>();
/// poco_assert (sub.get());
{
Other* pOther = dynamic_cast<Other*>(_ptr);
return AutoPtr<Other>(pOther, true);
}
template<class Other>
AutoPtr<Other> unsafeCast() const
/// Casts the AutoPtr via a static cast to the given type.
/// Example: (assume class Sub: public Super)
/// AutoPtr<Super> super(new Sub());
/// AutoPtr<Sub> sub = super.unsafeCast<Sub>();
/// poco_assert (sub.get());
{
Other* pOther = static_cast<Other*>(_ptr);
return AutoPtr<Other>(pOther, true);
}
C* operator ->() {
if (_ptr)
return _ptr;
else
throw NullPointerException();
}
const C* operator ->() const {
if (_ptr)
return _ptr;
else
throw NullPointerException();
}
C& operator *() {
if (_ptr)
return *_ptr;
else
throw NullPointerException();
}
const C& operator *() const {
if (_ptr)
return *_ptr;
else
throw NullPointerException();
}
C* get() {
return _ptr;
}
const C* get() const {
return _ptr;
}
operator C*() {
return _ptr;
}
operator const C*() const {
return _ptr;
}
bool operator !() const {
return _ptr == 0;
}
bool isNull() const {
return _ptr == 0;
}
C* duplicate() {
if (_ptr)
_ptr->duplicate();
return _ptr;
}
bool operator ==(const AutoPtr& ptr) const {
return _ptr == ptr._ptr;
}
bool operator ==(const C* ptr) const {
return _ptr == ptr;
}
bool operator ==(C* ptr) const {
return _ptr == ptr;
}
bool operator !=(const AutoPtr& ptr) const {
return _ptr != ptr._ptr;
}
bool operator !=(const C* ptr) const {
return _ptr != ptr;
}
bool operator !=(C* ptr) const {
return _ptr != ptr;
}
bool operator <(const AutoPtr& ptr) const {
return _ptr < ptr._ptr;
}
bool operator <(const C* ptr) const {
return _ptr < ptr;
}
bool operator <(C* ptr) const {
return _ptr < ptr;
}
bool operator <=(const AutoPtr& ptr) const {
return _ptr <= ptr._ptr;
}
bool operator <=(const C* ptr) const {
return _ptr <= ptr;
}
bool operator <=(C* ptr) const {
return _ptr <= ptr;
}
bool operator >(const AutoPtr& ptr) const {
return _ptr > ptr._ptr;
}
bool operator >(const C* ptr) const {
return _ptr > ptr;
}
bool operator >(C* ptr) const {
return _ptr > ptr;
}
bool operator >=(const AutoPtr& ptr) const {
return _ptr >= ptr._ptr;
}
bool operator >=(const C* ptr) const {
return _ptr >= ptr;
}
bool operator >=(C* ptr) const {
return _ptr >= ptr;
}
private:
C* _ptr;
};
template<class C>
inline void swap(AutoPtr<C>& p1, AutoPtr<C>& p2) {
p1.swap(p2);
}
这个模板类,主要实现了一些创建、复制、销毁动作及其它的一些操作符。创建、复制(copy构造函数及赋值操作符)及销毁动作是智能指针行为的核心之所在,创建、复制时,需要正确地增加对象的引用计数,而在销毁时,则要减少只能指针。
既然称之为智能指针,那自然就不能少了常规的指针都有的一些操作方式,比如解引用或通过箭头操作符访问对象成员,因而,不出意料的这个模板类也重载了operator ->和operator 这两个操作符。它还提供了类型智能指针到对象类型指针的转换操作operator C/operator const C*,及获取对象指针的操作。
除此之外,它还提供了一系列基于指针值的比较操作。
AutoPtr接收单个指针作为参数的构造函数,使得编译器可以实施由一个对象指针到智能指针的隐式类型转换。所造成的问题就是,一不小心,编译器自己创建了一个AutoPtr,但对象的引用计数没有增加,后续智能指针对象在销毁的时候,它所追踪的对象的引用计数会提前减小到0,然后对象会被提前释放。比如传递this指针,调用一个接受该对象的智能指针为参数的函数。但其实这种情况下,是应该要调用那个需要一个额外的bool型参数的构造函数来创建智能指针对象,以使得对象的引用计数能被适当的增加的。由使用者角度来看,Poco的AutoPtr的这种设计增加了用户使用它的风险,因而将AutoPtr的AutoPtr(C* ptr)构造函数声明为explicit以挡掉编译器的隐式类型转换似乎要更好,更安全一点。
android sp 的用法及实现
android sp 的用法
std::tr1::shared_ptr 的用法及实现
std::tr1::shared_ptr 的用法及实现
网友评论