最近在忙自己的研究生科研作業和盡量在不看原始碼的情況下寫一個玩具版的muduo(我已經看過陳碩的《Linux多執行緒服務端編程:使用muduo C++網路庫》,相當于按自己的理解再寫一遍),沒太有時間寫C++物件模型的后面部分,等組會開完后再繼續寫,
今天就寫一下幾天前看到的一個小技巧,也即標題:std::weak_ptr<void>系結到std::shared_ptr<T>,
std::weak_ptr
我們知道weak_ptr目的是防止只使用std::shared_ptr導致的回圈參考,從而導致記憶體泄漏,一個經典的例子如下:
#include <iostream>
#include <vector>
#include <memory>
#include <string>
class Child;
class Parent {
public:
Parent(const std::string& name)
: m_name(name),
m_children()
{}
~Parent();
void addChild(std::shared_ptr<Child>& child) {
m_children.push_back(child);
}
const std::string&
getName() const {
return m_name;
}
std::vector<std::shared_ptr<Child>>&
getChildren() {
return m_children;
}
private:
std::string m_name;
std::vector<std::shared_ptr<Child>> m_children; // Parent物件使用shared_ptr來持有Child物件
};
class Child {
public:
Child(const std::string& name, std::shared_ptr<Parent>& parent)
: m_name(name),
m_parent(parent)
{}
~Child() {
std::cout << m_name << "'s destruction" << std::endl;
}
void showParentName() const {
std::shared_ptr<Parent> parent = m_parent.lock();
if (parent) {
std::cout << m_name << "'s parent: " << parent->getName() << std::endl;
} else {
std::cout << m_name << "'s parent has destructed" << std::endl;
}
}
private:
std::string m_name;
std::weak_ptr<Parent> m_parent; // Child物件使用weak_ptr來參考Parent物件
};
Parent::~Parent() {
std::cout << m_name << "'s destruction" << std::endl;
}
void func() {
std::shared_ptr<Parent> parent = std::make_shared<Parent>("Parent01");
std::shared_ptr<Child> child = std::make_shared<Child>("Child01", parent);
parent->addChild(child);
child->showParentName();
}
int main() {
func();
}
// Output:
// Child01's parent: Parent01
// Parent01's destruction
// Child01's destruction
我們可以看到Parent和Child物件均正常析構了,
std::weak_ptr與其系結的std::shared_ptr
在上面的代碼中,如果有其他地方持有std::shared_ptr<Child>,那么在Parent析構時,被該std::share_ptr<Child>持有的Child物件不會析構,而且Child::showParentName會正常識別出其Parent物件已經被析構,這就是std::weak_ptr能判斷其系結的std::shared_ptr管理的物件是否已經析構,
但有一個問題,如果我只是用std::weak_ptr來判斷其系結的std::shared_ptr管理的物件是否已經析構,但其系結的std::shared_ptr管理的物件型別不一定怎么辦?正如標題所言,std::weak_ptr<void>可以系結到所有型別的std::shared_ptr,所以只要使用一個std::weak_ptr即可,
我知道這個用法的來源是陳碩的muduo網路庫,
在muduo中,類Channel用于管理一個socket描述符的讀、寫、出錯事件,并呼叫相應的回呼,
但有一個問題是Channel類并不持有該socket描述符(只存有該socket描述符,但其生命期并不歸Channel管理),那如何判斷Channel對應的管理socket描述符的類是否已經析構呢(因為Channel的讀寫出錯回呼往往是通過std::bind或者lambda包裹的socket描述符的持有者的private方法,如果持有者已經析構,再呼叫回呼會導致段錯誤從而core dump)?
muduo就是在Channel中使用std::weak_ptr<void>,其有一個方法Channel::tie,接受const std::shared_ptr<void>&型別的引數,此引數要求傳入持有socket描述符管理者物件的std::shared_ptr,muduo將此引數賦值給給std::weak_ptr<void>物件,使其可以監控socket描述符管理者物件是否已經析構,部分代碼如下:
// muduo/net/Channel.cc
void Channel::tie(const std::shared_ptr<void>& obj)
{
tie_ = obj; // std::weak_ptr<void> tie_
tied_ = true; // bool tied_
}
void Channel::handleEvent(Timestamp receiveTime)
{
std::shared_ptr<void> guard;
if (tied_)
{
guard = tie_.lock();
if (guard)
{
handleEventWithGuard(receiveTime);
}
}
else
{
handleEventWithGuard(receiveTime);
}
}
這樣的用法是合法的嗎?我們可以在cppreference上查看一下std::shared_ptr和std::weak_ptr的相關資訊,
可以看到std::shared_ptr有如下的建構式:
// https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr
template<typename T> // 這兩行是我自己加的,
class std::shared_ptr { // 說明里面是該類的成員函式
// ... (1) - (2)
template< class Y >
explicit shared_ptr( Y* ptr ); // (3)
// ... (4)-(13)
};
可以看到可以由std::shared_ptr<Y>構造std::shared_ptr<T>,要求是:
For (3-4,6), Y* must be convertible to T*. // until C++17
也就是只要Y*能轉化為T*即可,而一般的指標型別(除了成員指標和成員函式指標)都可以轉化為void*,所以std::shared_ptr<T>構造std::shared_ptr<void>是可以的,而且他們管理著相同的物件,測驗如下:
#include <iostream>
#include <memory>
class Test {
public:
~Test() {
std::cout << "Test::~Test()" << std::endl;
}
};
int main() {
std::shared_ptr<void> pvoid;
{
std::shared_ptr<Test> pTest = std::make_shared<Test>();
pvoid = pTest;
}
std::cout << "pTest has destructed" << std::endl;
}
// Output:
// pTest has destructed
// Test::~Test()
然后std::shared_ptr<void>構造std::weak_ptr<void>就是理所當然的了,
那能不能由std::shared_ptr<T>直接構造std::weak_ptr<void>呢?按理來說是可以的,我們在cppreference里面找一下可以發現:
// https://en.cppreference.com/w/cpp/memory/weak_ptr/weak_ptr
template<T> // 這兩行是我自己加的,
std::weak_ptr { // 說明里面是該類的成員函式
template< class Y >
weak_ptr( const std::shared_ptr<Y>& r ) noexcept;
};
The templated overloads don't participate in the overload resolution unless Y* is implicitly convertible to T*
即如果Y*能隱式轉化為T*的話是可以的,而我們知道一般的指標型別(除了成員指標和成員函式指標)都可以隱式轉化為void*型別,所以由std::share_ptr<T>構造std::weak_ptr<void>是可行的,驗證如下:
#include <iostream>
#include <memory>
class Test {
public:
~Test() {
std::cout << "Test::~Test()" << std::endl;
}
};
std::weak_ptr<void> func() {
std::shared_ptr<Test> pTest = std::make_shared<Test>();
std::weak_ptr<void> pVoidWeak = pTest;
std::shared_ptr<void> pVoid = pVoidWeak.lock();
if (pVoid) {
std::cout << "Test object exists" << std::endl;
} else {
std::cout << "Test object has been destructed" << std::endl;
}
return pVoidWeak;
}
int main() {
auto pVoidWeak = func();
std::shared_ptr<void> pVoid = pVoidWeak.lock();
if (pVoid) {
std::cout << "Test object exists" << std::endl;
} else {
std::cout << "Test object has been destructed" << std::endl;
}
}
// Output:
// Test object exists
// Test::~Test()
// Test object has been destructed
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/526768.html
標籤:C++
