前言 我們在啟動 Spring Boot 項目時,控制台會列印出 Spring Boot 專屬的標語,也稱 banner(橫幅標語/廣告),效果如下: 實際上,上面這個 banner,我們可以自定義,而很多公司也有使用自己的 banner 的。 下麵介紹在 Spring Boot 項目中使用自定義 ...
最近在忙自己的研究生科研工作和儘量在不看源碼的情況下寫一個玩具版的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