1. weak_ptr 介紹 std::weak_ptr 是一種智能指針,它對被 std::shared_ptr 管理的對象存在非擁有性("弱")引用。在訪問所引用的對象指針前必須先轉換為 std::shared_ptr。 主要用來表示臨時所有權,當某個對象存在時才需要被訪問。轉換為shared_p ...
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..
我們可以看到,weak_ptr多次通過lock轉換成shared_ptr,獲得shared_ptr後可以成功調用管理對象的方法,這個過程中引用計數是在增加的,因此如果原來的shared_ptr銷毀是不影響你這個臨時對象使用, 資源析構正常。 下麵是gnu STL 庫中最後調用lock函數會跑到的地方,引用計數 + 1 。(GNU STL 文件:shared_ptr_base.h)
2. weak_ptr 實現和迴圈引用問題
1. shared_ptr 迴圈引用問題
我們首先看一下迴圈引用的問題,具體代碼如下:
測試類:
#include <memory>
#include <iostream>
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<foo> fooptr;
};
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<Test> testptr;
};
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
從列印結果可以很明顯的看出,經過迴圈引用, 對象引用計數變成了2,並且執行完後,資源沒有釋放,沒有調用類的destruct(析構)函數。
將類中的std::shared_ptr
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<foo> fooptr;
};
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<Test> testptr;
};
再看下weak_ptr的執行結果,可以看到計數正常,資源成功釋放:
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..
2. weak_ptr 實現
我們這邊貼一下weak_ptr類的代碼:
template <class T>
class weak_ptr
{
public:
template <class S>
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;
};
主要註意的是lock函數,如果計數指針為空,那麼會返回一個空的shared_ptr,然後就是不能重載operator*和operator-> 操作符。
主要參考: cppreference.com
完整實現見:smart_ptr
3. enable_shared_from_this
這邊還有一個點也要介紹一下,那就是enable_shared_from_this,這個主要是為了處理在shared_ptr管理的對象中要使用該對象的指針所引出的問題。 我們看下下麵這個例子:
class foo
{
public:
std::shared_ptr<foo> getptr()
{
// 如果類中要返回自己的指針怎麼辦?
return std::shared_ptr<foo>(this);
}
~foo()
{
std::cout << "foo destruct .. " << std::endl;
}
};
int main()
{
std::shared_ptr<foo> bp1(new foo());
bp1->getptr();
std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
}
看下結果,釋放兩次:
ash-4.2$ ./share_ptr
foo destruct ..
bp1.use_count() = 1
foo destruct ..
其實我們都不用測試,因為你如果直接使用該對象的this指針又拷貝給另一個shared_ptr,那不就等於兩個沒有關係的shared_ptr管理同一個對象了嗎? 釋放的時候等於會調用兩次該對象的析構函數。enable_shared_from_this就是用來解決這個問題的。看下代碼:
class foo : public std::enable_shared_from_this<foo>
{
public:
std::shared_ptr<foo> getptr()
{
return shared_from_this();
}
~foo()
{
std::cout << "foo destruct .. " << std::endl;
}
};
int main()
{
std::shared_ptr<foo> bp1(new foo());
bp1->getptr();
std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
}
看下結果,成功釋放:
bash-4.2$ ./share_ptr
bp1.use_count() = 1
foo destruct ..
總結一下,weak_ptr本質是以一種觀察者的形象存在,它可以獲取到觀察主體的狀態,但是無法獲取直接獲取到觀察主體,無法直接對觀察主體修改,無法釋放觀察主體的資源,你只能通過轉換成shared_ptr來做一些事情。 和觀察者模式很像,訂閱,得到觀察主體狀態,在多線程環境下會比較管用!
2018年9月30日00:40:02