資源管理在軟件開發(fā)中歷來都是老大難問題,堆上內(nèi)存的管理尤其麻煩。現(xiàn)代編程語言引入了垃圾回收機(jī)制,如 Java,Python,Go 等,在內(nèi)存管理方面為開發(fā)者提供了極大的便利,開發(fā)者面對內(nèi)存泄漏問題的麻煩大大減少。
現(xiàn)代 C++ 的一大進(jìn)步就是在資源管理方面。開發(fā)者可以很方便地將資源的生命周期管理,如堆上內(nèi)存,文件描述符,Socket 句柄等,轉(zhuǎn)化為對象的生命周期管理,并借助于 C++ 的對象析夠機(jī)制,使得資源的生命周期管理大為簡化。
相對于其它現(xiàn)代編程語言,C++ 提供了操作符重載這種強(qiáng)大的工具,將普通的操作符與函數(shù)統(tǒng)一起來,使得資源管理更為方便。C++ 的對象析夠機(jī)制,總是能夠確保一個對象在超出它的作用域之后被釋放掉,如函數(shù)的局部對象在函數(shù)返回后必然被析夠,堆上的對象可以確保在調(diào)用它的 delete 之后被釋放掉,這相對于垃圾回收機(jī)制更為可靠。本文主要討論堆上內(nèi)存的管理。

首先來看一下堆上內(nèi)存管理的難點。堆上對象的分配,通常不是什么太大的問題,分配的點總是比較容易分析,比較麻煩的是對象的釋放。如果一個對象只被另外一個對象使用,則對象的生命周期管理也比較容易。當(dāng)一個堆上分配的對象,同時為多個其它對象,特別是同時為多個不同線程訪問時,釋放時機(jī)的選擇就比較棘手了。
一個對象持有指向某個堆上對象的指針時,它根本無法判斷這個對象是否已經(jīng)被釋放。一個堆上對象被某個對象釋放,其它對象再去訪問它時,比較好的情況是,程序立即由于 SIGSEGV 而崩潰,更糟糕的情況則是,程序在一段時間內(nèi)繼續(xù)運行,最后在莫名其妙的位置發(fā)生崩潰或者死鎖,這還會給問題診斷制造麻煩。
如果訪問某個堆上對象的所有對象都沒有去釋放它,則將會造成內(nèi)存泄漏。內(nèi)存資源將隨著程序運行時間的流逝,而逐漸被消耗殆盡。
現(xiàn)代 C++ 通過智能指針管理堆上內(nèi)存。智能指針并不是真正的指針,而是一個類對象,但它的行為基本上就像普通的指針那樣。通常我們對指針?biāo)龅牟僮髦饕校和ㄟ^解引用操作符 -> 訪問指針指向的對象的成員,通過 * 操作符獲得指針指向的對象的引用,復(fù)制,邏輯判斷如相等性判斷、不等性判斷等,指針比較等。智能指針可以提供所有這些操作,但不像普通指針那樣由編程語言提供,而是借助于操作符重載,實現(xiàn)響應(yīng)的操作符函數(shù)。
在智能指針的使用上,通常我們先在堆上分配一個對象,將指向?qū)ο蟮闹羔樈o智能指針對象,智能指針對象在釋放時,根據(jù)需要釋放我們在堆上分配的對象。以此堆上資源生命周期管理問題被轉(zhuǎn)換為了智能指針對象的生命周期管理問題。
當(dāng)然智能指針也有不好的地方,或者說存在風(fēng)險的地方。如果兩個用智能指針管理的對象,它們相互之間通過智能指針引用對方,將難免會造成內(nèi)存泄漏。要避免這種問題,需要仔細(xì)地考慮對象間的關(guān)系,并適當(dāng)使用 weak_ptr。智能指針的使用也還是需要多加小心。
本文討論智能指針的使用和實現(xiàn)。C++ 中的智能指針,就對待所管理指針的方式而言,可分為兩大類。一是如 C++ STL 中的 std::unique_ptr,boost 的 boost::scoped_ptr 和 boost::scoped_array 這樣,在智能指針對象銷毀時,直接釋放所管理的堆上對象的。二是如 C++ STL 中的 std::shared_ptr ,Poco 庫的 Poco::AutoPtr, Android framework 中的 sp 這樣,基于引用計數(shù)實現(xiàn),在智能指針對象復(fù)制時增加引用計數(shù),在智能指針對象釋放時減小引用計數(shù),只有當(dāng)引用計數(shù)減小為 0 時,才釋放對象的。
基于引用計數(shù)實現(xiàn)的智能指針,依據(jù)其侵入性的不同,又分為兩類:一是具有一定的侵入性,要求被智能指針管理的對象的類繼承某個特定類的,如 Poco 庫里的 Poco::AutoPtr 和 android 中的 sp;二是對被管理的對象沒有任何要求的,如 C++ STL 中的 std::shared_ptr。
本文分析幾種智能指針的使用與實現(xiàn):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 實現(xiàn)。
boost::scoped_ptr 的使用及實現(xiàn)
先來看一下 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;
}
在創(chuàng)建 boost::scoped_ptr 對象的時候,把需要由它管理的堆上對象的指針傳給構(gòu)造函數(shù),在 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 的用法及實現(xiàn)
先來看一下用法:
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執(zhí)行的結(jié)果:
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
可以看到對象的引用計數(shù)隨著流程的執(zhí)行而改變,AutoPtr對象每復(fù)制一次,其管理的對象的引用計數(shù)就加一,而AutoPtr對象每釋放一次,其管理的對象的引用計數(shù)就減一。
接著來具體說明一下要如何使用AutoPtr。首先,由智能指針來管理其對象釋放的class需要繼承Poco庫的RefCountedObject。AutoPtr是一種基于引用計數(shù)的堆對象管理機(jī)制,因而,總要有個地方來存放一個對象的引用次數(shù)。AutoPtr管理的對象,用于記錄引用計數(shù)的變量存放在該對象中,但那個變量繼承自RefCountedObject(RefCountedObject提供的當(dāng)然不止是這樣的一個變量)。
一定要繼承RefCountedObject才能使用Poco的AutoPtr嗎?答案是不一定。但要由AutoPtr管理其對象的類,一定要符合模板AutoPtr所要求的隱式接口。具體點說,就是要提供RefCountedObject提供的接口,就像網(wǎng)上有些地方看到的例子,讓那些類自己定義那些函數(shù)。當(dāng)然接口的行為也要與RefCountedObject基本一致,比如,增加引用計數(shù),減少引用計數(shù),在引用計數(shù)減小為0時釋放對象,否則的話,AutoPtr的作用也不能真正展現(xiàn)。
然后就可以RAII(資源獲取即初始化),使用AutoPtr來管理new的對象了。我們可以放心的像使用Java里面的對象一樣使用AutoPtr,而不用擔(dān)心會有memory leak。
接著我們來看Poco::AutoPtr這套機(jī)制的實現(xiàn)。主要就是class RefCountedObject和模板AutoPtr的定義。先來看RefCountedObject的定義與實現(xiàn):
#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的實現(xiàn)文件:
RefCountedObject::RefCountedObject(): _counter(1)
{
}
RefCountedObject::~RefCountedObject()
{
}
這個類只有一個原子類型AtomicCounter的成員變量_counter,用來記錄對象被引用的次數(shù)??梢钥吹剿潜宦暶鳛閙utable的,也就是說,即使是對于const的對象,在const方法中,也可以修改這個變量的值。
這個類提供了三個操作duplicate()、release()和referenceCount(),用來修改或讀取_counter的值。這三個操作全都被聲明為const,配合聲明為mutable的_counter,使得AutoPtr這套機(jī)制,即使是對于new的const class也一樣可用。duplicate()操作中,會增加引用計數(shù),release()操作中會減少引用計數(shù),referenceCount()操作則會將對象當(dāng)前被引用的次數(shù)返回給調(diào)用者。在release()操作中,如果引用技術(shù)減到了0,當(dāng)前對象會被delete掉。這些操作究竟會在什么地方調(diào)用到呢?答案是AutoPtr,后面看到模板AutoPtr的定義就能明白了。
這個class還采用了一些方法來阻止對于它的繼承體系中對象的復(fù)制行為??梢钥吹?,它聲明了private的copy構(gòu)造函數(shù)和賦值操作符,但兩個函數(shù)都沒有被定義。private聲明可以在編譯期阻止那些對于這個class的private成員沒有訪問權(quán)限的部分復(fù)制對象,而有意的不定義這兩個函數(shù),則可以在鏈接期阻止對這個class的private成員有訪問權(quán)限的部分,比如一些friend class,friend方法,或內(nèi)部的一些函數(shù)復(fù)制對象。
接著來看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);
}
這個模板類,主要實現(xiàn)了一些創(chuàng)建、復(fù)制、銷毀動作及其它的一些操作符。創(chuàng)建、復(fù)制(copy構(gòu)造函數(shù)及賦值操作符)及銷毀動作是智能指針行為的核心之所在,創(chuàng)建、復(fù)制時,需要正確地增加對象的引用計數(shù),而在銷毀時,則要減少只能指針。
既然稱之為智能指針,那自然就不能少了常規(guī)的指針都有的一些操作方式,比如解引用或通過箭頭操作符訪問對象成員,因而,不出意料的這個模板類也重載了operator ->和operator 這兩個操作符。它還提供了類型智能指針到對象類型指針的轉(zhuǎn)換操作operator C/operator const C*,及獲取對象指針的操作。
除此之外,它還提供了一系列基于指針值的比較操作。
AutoPtr接收單個指針作為參數(shù)的構(gòu)造函數(shù),使得編譯器可以實施由一個對象指針到智能指針的隱式類型轉(zhuǎn)換。所造成的問題就是,一不小心,編譯器自己創(chuàng)建了一個AutoPtr,但對象的引用計數(shù)沒有增加,后續(xù)智能指針對象在銷毀的時候,它所追蹤的對象的引用計數(shù)會提前減小到0,然后對象會被提前釋放。比如傳遞this指針,調(diào)用一個接受該對象的智能指針為參數(shù)的函數(shù)。但其實這種情況下,是應(yīng)該要調(diào)用那個需要一個額外的bool型參數(shù)的構(gòu)造函數(shù)來創(chuàng)建智能指針對象,以使得對象的引用計數(shù)能被適當(dāng)?shù)脑黾拥摹S墒褂谜呓嵌葋砜?,Poco的AutoPtr的這種設(shè)計增加了用戶使用它的風(fēng)險,因而將AutoPtr的AutoPtr(C* ptr)構(gòu)造函數(shù)聲明為explicit以擋掉編譯器的隱式類型轉(zhuǎn)換似乎要更好,更安全一點。
android sp 的用法及實現(xiàn)
android sp 的用法
std::tr1::shared_ptr 的用法及實現(xiàn)
std::tr1::shared_ptr 的用法及實現(xiàn)