自從C++11有了std::shared_ptr這樣的智能指針,作爲C++程序只要將一個堆上的類對象用std::shared_ptr包裹一下就可以做到內存自動釋放了。看一個例子:
#include "stdafx.h"
#include <memory>
class A
{
public:
A()
{
m_i = 9;
}
~A()
{
m_i = 0;
}
public:
int m_i;
};
int _tmain(int argc, _TCHAR* argv[])
{
{
std::shared_ptr<A> spa(new A());
}
return 0;
}
但是假如,我們有一些開發需求中(也可能是前同事遺留下的代碼),我們需要在一個類中引用自身,即一個類的一個成員變量是一個std::shared_ptr對象,它引用了類對象自身,這裏分爲兩種情況,第一種情況是類對象是棧對象,第二種情況是類對象是堆對象。
我們先看類對象是棧對象的情形,示例代碼如下:
#include "stdafx.h"
#include <memory>
class A : public std::enable_shared_from_this<A>
{
public:
A()
{
m_i = 9;
//注意:
//比較好的做法是在構造函數裏面調用shared_from_this()給m_SelfPtr賦值
//但是很遺憾不能這麼做,如果寫在構造函數裏面程序會直接崩潰
}
~A()
{
m_i = 0;
}
void func()
{
m_SelfPtr = shared_from_this();
}
public:
int m_i;
std::shared_ptr<A> m_SelfPtr;
};
int _tmain(int argc, _TCHAR* argv[])
{
{
A a;
a.func();
}
return 0;
}
上面的代碼,在調用a.func()時程序會直接崩潰,崩潰的原因是調用shared_from_this()函數裏面,看一下崩潰的調用堆棧:
我們來看下崩潰的原因,看下shared_from_this()函數的調用細節:
也就是說shared_from_this()函數內部會先調用shared_ptr的構造函數去構造一個shared_ptr對象,參數是自己的成員變量_Wptr,這是一個std::weak_ptr:
private:
template<class _Ty1,
class _Ty2>
friend void _Do_enable(
_Ty1 *,
enable_shared_from_this<_Ty2>*,
_Ref_count_base *);
mutable weak_ptr<_Ty> _Wptr;
而shared_ptr的構造函數裏面又會調用reset()先釋放之前的對象引用,如果這個之前的對象就是_Wptr這個指針去引用,但是現在_Wptr是空的,就拋出一個異常。_Wptr之所以爲空,是這個指針引用的對象並沒有被任何智能指針所包裹(A的對象a是棧變量)。這就是崩潰的原因。
我們來接着看下A對象是堆對象的情形:
class A : public std::enable_shared_from_this<A>
{
public:
A()
{
m_i = 9;
//注意:
//比較好的做法是在構造函數裏面調用shared_from_this()給m_SelfPtr賦值
//但是很遺憾不能這麼做,如果寫在構造函數裏面程序會直接崩潰
}
~A()
{
m_i = 0;
}
void func()
{
m_SelfPtr = shared_from_this();
}
public:
int m_i;
std::shared_ptr<A> m_SelfPtr;
};
int _tmain(int argc, _TCHAR* argv[])
{
{
std::shared_ptr<A> spa(new A());
spa->func();
}
return 0;
}
這次不會崩潰了,但是遺憾的是,這個new出來的A對象的堆內存再也不會釋放了(當然程序退出靠操作系統回收不算)。爲啥不會釋放呢?我們來分析下原因:
要想堆上的A被釋放,那麼至少需要所有指向A的std::shared_ptr對象都不再引用A,但是A的成員變量只有在A自己被釋放的時候纔會不再引用A。反過來說,A的成員變量m_SelfPtr等着A對象本身釋放,而A作爲堆對象釋放的條件是所有引用它的的std::shared_ptr釋放。這就相互矛盾了。這種情形導致,這樣的A對象永遠不會被自動釋放。我們使用std::weak_ptr來看看最終這個A的引用計數是多少:
#include "stdafx.h"
#include <memory>
class A : public std::enable_shared_from_this<A>
{
public:
A()
{
m_i = 9;
//注意:
//比較好的做法是在構造函數裏面調用shared_from_this()給m_SelfPtr賦值
//但是很遺憾不能這麼做,如果寫在構造函數裏面程序會直接崩潰
}
~A()
{
m_i = 0;
}
void func()
{
m_SelfPtr = shared_from_this();
}
public:
int m_i;
std::shared_ptr<A> m_SelfPtr;
};
int _tmain(int argc, _TCHAR* argv[])
{
std::weak_ptr<A> spwa;
{
std::shared_ptr<A> spa(new A());
spa->func();
spwa = spa;
}
printf("spwa usecount: %d\n", spwa.use_count());
return 0;
}
確實和我們分析的一樣,這個堆上的A引用計數永遠是A了,所以不會被釋放了。
那有什麼解決方案呢?
我們可以在增加一個成員函數,在不需要A時,主動釋放這個智能指針的成員變量引用的對象:
#include "stdafx.h"
#include <memory>
class A : public std::enable_shared_from_this<A>
{
public:
A()
{
m_i = 9;
//注意:
//比較好的做法是在構造函數裏面調用shared_from_this()給m_SelfPtr賦值
//但是很遺憾不能這麼做,如果寫在構造函數裏面程序會直接崩潰
}
~A()
{
m_i = 0;
}
void func()
{
m_SelfPtr = shared_from_this();
}
void release()
{
m_SelfPtr.reset();
}
public:
int m_i;
std::shared_ptr<A> m_SelfPtr;
};
int _tmain(int argc, _TCHAR* argv[])
{
std::weak_ptr<A> spwa;
{
std::shared_ptr<A> spa(new A());
spa->func();
spa->release();
spwa = spa;
}
printf("spwa usecount: %d\n", spwa.use_count());
return 0;
}
這樣,程序就會自動調用A的析構函數來釋放自己呢。但是!!!這樣人爲地增加一個release()函數相當於手工調用了delete,使用智能指針還有什麼意義,我們還得人工管理內存釋放。
綜合下來,這種在對象內部引用自己的智能指針是一種非常不好的設計,個人覺得還是要杜絕這種錯誤的用法。