1. weak_ptr 介紹
std::weak_ptr 是一種智能指針,它對被 std::shared_ptr 管理的對象存在非擁有性("弱")引用。在訪問所引用的對象指針前必須先轉換為 std::shared_ptr。 主要用來表示臨時所有權,當某個對象存在時才需要被訪問。轉換為shared_ptr的過程等於對象的shared_ptr 的引用計數加一,因此如果你使用weak_ptr獲得所有權的過程中,原來的shared_ptr被銷毀,則該對象的生命期會被延長至這個臨時的 std::shared_ptr 被銷毀為止。 weak_ptr還可以避免 std::shared_ptr 的循環引用。
std::weak_ptr簡單使用:(編譯系統:Linux centos 7.0 x86_64 編譯器:gcc 4.8.5 )
#include <memory>
#include <iostream>
class foo
{
public:
foo()
{
std::cout << "foo construct.." << std::endl;
}
void method()
{
std::cout << "welcome Test foo.." << std::endl;
}
~foo()
{
std::cout << "foo destruct.." << std::endl;
}
};
int main()
{
// weak_ptr
foo* foo2 = new foo();
// share_ptr 管理對象
std::shared_ptr<foo> shptr_foo2(foo2);
// weak_ptr 弱引用
std::weak_ptr<foo> weak_foo2(shptr_foo2);
// 如果要獲取數據指針,需要通過lock接口獲取
weak_foo2.lock()->method();
std::shared_ptr<foo> tmp = weak_foo2.lock();
// 我們這邊有嘗試多次獲取所有權(lock),看一下引用計數個數
std::cout << "shptr_foo2 RefCount: " << weak_foo2.lock().use_count() << std::endl;
return 0;
}
執行結果:
``` bash-4.2$ ./share_ptr foo construct.. welcome Test foo.. shptr_foo2 RefCount: 3 foo destruct..
<p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微軟雅黑';">我們可以看到,weak_ptr多次通過lock轉換成shared_ptr,獲得shared_ptr后可以成功調用管理對象的方法,這個過程中<font color="#ff0000">引用計數是在增加的,因此如果原來的shared_ptr銷毀是不影響你這個臨時對象使用, 資源析構正常</font>。 下面是gnu STL 庫中最后調用lock函數會跑到的地方,引用計數 + 1 。(GNU STL 文件:shared_ptr_base.h) </p>

<p style="color: #AD5D0F;font-weight: bold;font-size: 20px; font-family: '微軟雅黑';">2. weak_ptr 實現和循環引用問題</p>
------
<p style="font-size: 15px; letter-spacing:1px; font-weight: bold; font-family: '微軟雅黑';">1. shared_ptr 循環引用問題</p>
<p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微軟雅黑';">我們首先看一下循環引用的問題,具體代碼如下: </p>
測試類:
include
include
class foo;
class Test
{
public:
Test()
{
std::cout << "construct.." << std::endl;
}
void method()
{
std::cout << "welcome Test.." << std::endl;
}
~Test()
{
std::cout << "destruct.." << std::endl;
}
public:
std::shared_ptr
};
class foo
{
public:
foo()
{
std::cout << "foo construct.." << std::endl;
}
void method()
{
std::cout << "welcome Test foo.." << std::endl;
}
~foo()
{
std::cout << "foo destruct.." << std::endl;
}
public:
std::shared_ptr
};
main函數:
int main()
{
// 循環引用 測試
Test* t2 = new Test();
foo* foo1 = new foo();
std::shared_ptr<Test> shptr_Test(t2);
std::shared_ptr<foo> shptr_foo(foo1);
std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;
std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;
shptr_Test->fooptr = shptr_foo;
shptr_foo->testptr = shptr_Test;
std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;
std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;
return 0;
}
測試結果:
bash-4.2$ ./share_ptr
construct..
foo construct..
shptr_Test RefCount: 1
shptr_foo RefCount: 1
shptr_Test RefCount: 2
shptr_foo RefCount: 2
<p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微軟雅黑';">從打印結果可以很明顯的看出,經過循環引用, 對象引用計數變成了2,並且執行完后,<font color="#ff0000">資源沒有釋放,沒有調用類的destruct(析構)函數</font>。</p>
<p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微軟雅黑';">將類中的std::shared_ptr<foo> 換成 std::weak_ptr<foo>:</p>
class foo;
class Test
{
public:
Test()
{
std::cout << "construct.." << std::endl;
}
void method()
{
std::cout << "welcome Test.." << std::endl;
}
~Test()
{
std::cout << "destruct.." << std::endl;
}
public:
std::weak_ptr
};
class foo
{
public:
foo()
{
std::cout << "foo construct.." << std::endl;
}
void method()
{
std::cout << "welcome Test foo.." << std::endl;
}
~foo()
{
std::cout << "foo destruct.." << std::endl;
}
public:
std::weak_ptr
};
<p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微軟雅黑';">再看下weak_ptr的執行結果,可以看到<font color="#ff0000">計數正常,資源成功釋放</font>:</p>
bash-4.2$ ./share_ptr
construct..
foo construct..
shptr_Test RefCount: 1
shptr_foo RefCount: 1
shptr_Test RefCount: 1
shptr_foo RefCount: 1
foo destruct..
destruct..
<p style="font-size: 15px; letter-spacing:1px; font-weight: bold; font-family: '微軟雅黑';">2. weak_ptr 實現</p>
<p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微軟雅黑';">我們這邊貼一下weak_ptr類的代碼:</p>
template
class weak_ptr
{
public:
template
friend class weak_ptr;
template <class S>
friend class shared_ptr;
constexpr weak_ptr() noexcept : m_iWeakRefCount(nullptr), m_ptr(nullptr) { }
weak_ptr( const weak_ptr<T>& rhs ) noexcept : m_iWeakRefCount(rhs.m_iWeakRefCount)
{
m_ptr = rhs.lock().getPointer();
}
weak_ptr( const shared_ptr<T>& rhs ) noexcept
: m_iWeakRefCount(rhs.m_iRefCount), m_ptr(rhs.m_ptr) { }
template <typename S>
weak_ptr & operator=(const shared_ptr<S> & rhs)
{
m_ptr = dynamic_cast<T *>(rhs.m_ptr);
m_iWeakRefCount = rhs.m_iRefCount;
return *this;
}
template <typename S>
weak_ptr & operator=(const weak_ptr<S> & rhs)
{
m_ptr = dynamic_cast<T *>(rhs.m_ptr);
m_iWeakRefCount = rhs.m_iWeakRefCount;
return *this;
}
weak_ptr& operator=( const weak_ptr& rhs ) noexcept
{
m_iWeakRefCount = rhs.m_iWeakRefCount;
m_ptr = rhs.m_ptr;
return *this;
}
weak_ptr& operator=( const shared_ptr<T>& rhs ) noexcept
{
m_iWeakRefCount = rhs.m_iRefCount;
m_ptr = rhs.m_ptr;
return *this;
}
shared_ptr<T> lock() const noexcept
{
shared_ptr<T> tmp;
if(m_iWeakRefCount && *m_iWeakRefCount > 0)
{
tmp.m_iRefCount = m_iWeakRefCount;
tmp.m_ptr = m_ptr;
if(tmp.m_iRefCount)
{
++(*tmp.m_iRefCount);
}
}
return tmp;
}
int use_count()
{
return *m_iWeakRefCount;
}
bool expired() const noexcept
{
return *m_iWeakRefCount == 0;
}
void Reset()
{
m_ptr = NULL;
m_iWeakRefCount = NULL;
}
private:
int * m_iWeakRefCount;
T* m_ptr;
};
<p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微軟雅黑';">主要注意的是lock函數,如果計數指針為空,那么會返回一個空的shared_ptr,然后就是<font color="#ff0000">不能重載operator*和operator-> 操作符</font>。</p>
主要參考: <a href="https://zh.cppreference.com/w/cpp/memory/weak_ptr" target="_red"><font color=#00ffff size=5>cppreference.com</font></a>
完整實現見:<a href="https://github.com/Yejy813/stl_implement/tree/master/smart_ptr" target="_red"><font color=#00ffff size=5>smart_ptr</font></a>
<p style="font-size: 15px; letter-spacing:1px; font-weight: bold; font-family: '微軟雅黑';">3. enable_shared_from_this</p>
<p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微軟雅黑';">這邊還有一個點也要介紹一下,那就是enable_shared_from_this,這個主要是為了處理<font color="#ff0000">在shared_ptr管理的對象中要使用該對象的指針所引出的問題</font>。 我們看下下面這個例子: </p>
class foo
{
public:
std::shared_ptr
{
// 如果類中要返回自己的指針怎么辦?
return std::shared_ptr
}
~foo()
{
std::cout << "foo destruct .. " << std::endl;
}
};
int main()
{
std::shared_ptr
bp1->getptr();
std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
}
<p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微軟雅黑';"><font color="#ff0000">看下結果,釋放兩次</font>:</p>
ash-4.2$ ./share_ptr
foo destruct ..
bp1.use_count() = 1
foo destruct ..
<p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微軟雅黑';">其實我們都不用測試,因為你如果<font color="#ff0000">直接使用該對象的this指針又拷貝給另一個shared_ptr,那不就等於兩個沒有關系的shared_ptr管理同一個對象了嗎? 釋放的時候等於會調用兩次該對象的析構函數</font>。enable_shared_from_this就是用來解決這個問題的。看下代碼:</p>
class foo : public std::enable_shared_from_this
{
public:
std::shared_ptr
{
return shared_from_this();
}
~foo()
{
std::cout << "foo destruct .. " << std::endl;
}
};
int main()
{
std::shared_ptr
bp1->getptr();
std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
}
<p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微軟雅黑';">看下結果,成功釋放:</p>
bash-4.2$ ./share_ptr
bp1.use_count() = 1
foo destruct ..
<p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-weight: bold; font-family: '微軟雅黑';"><font color="#ff0000">總結一下,weak_ptr本質是以一種觀察者的形象存在,它可以獲取到觀察主體的狀態,但是無法獲取直接獲取到觀察主體,無法直接對觀察主體修改,無法釋放觀察主體的資源,你只能通過轉換成shared_ptr來做一些事情。 和觀察者模式很像,訂閱,得到觀察主體狀態,在多線程環境下會比較管用! </font></p>
<p style="font-size: 15px;text-indent:60em;letter-spacing:1px; font-family: '微軟雅黑';">2018年9月30日00:40:02</p>
