shared_ptr的主要優點是當不再使用時會自動釋放相關的記憶體。
但是如果我們不仔細使用shared_ptr,那麼這個優勢就會變成一個劣勢。 我們來看看:
假設我設計一個二叉樹,并在其中包含一個指向左右子節點的指針。

#include <iostream>
#include <memory>
class Node {
int value;
public:
std::shared_ptr<Node> leftPtr;
std::shared_ptr<Node> rightPtr;
Node(int val) : value(val) {
std::cout << "Constructor" << std::endl;
}
~Node() {
std::cout << "Destructor" << std::endl;
}
};
int main() {
std::shared_ptr<Node> ptr = std::make_shared<Node>(4);
ptr->leftPtr = std::make_shared<Node>(2);
ptr->rightPtr = std::make_shared<Node>(5);
return 0;
}
上面的例子運作正常。
調用3次構造函數和3次析構函數。這意味着完整的記憶體被删除。
但是,如果我們添加另一個小的需求,即每個節點将包含一個指向父節點的指針。 那麼它會導緻shared_ptr的問題。
檢視修改過的代碼
#include <iostream>
#include <memory>
class Node {
int value;
public:
std::shared_ptr<Node> leftPtr;
std::shared_ptr<Node> rightPtr;
std::shared_ptr<Node> parentPtr;
Node(int val) : value(val) {
std::cout << "Constructor" << std::endl;
}
~Node() {
std::cout << "Destructor" << std::endl;
}
};
int main() {
std::shared_ptr<Node> ptr = std::make_shared<Node>(4);
ptr->leftPtr = std::make_shared<Node>(2);
ptr->leftPtr->parentPtr = ptr;
ptr->rightPtr = std::make_shared<Node>(5);
ptr->rightPtr->parentPtr = ptr;
std::cout << "ptr reference count = " << ptr.use_count() << std::endl;
std::cout << "ptr->leftPtr reference count = " << ptr->leftPtr.use_count() << std::endl;
std::cout << "ptr->rightPtr reference count = " << ptr->rightPtr.use_count() << std::endl;
return 0;
}
輸出:
Constructor
Constructor
Constructor
ptr reference count = 1
ptr->leftPtr reference count = 1
ptr->rightPtr reference count = 1
現在構造函數會被調用3次,但是不會調用析構函數,這意味着記憶體洩漏。
導緻這個shared_ptr問題的原因是循環引用,即:
如果兩個對象使用shared_ptrs互相引用,那麼當超出範圍時,都不會删除記憶體。
發生這種情況是因為shared_ptr在其析構函數中遞減關聯記憶體檢查的引用計數之後,如果count為0,則删除該記憶體,如果大于1,則意味着其他shared_ptr正在使用此記憶體。
但是在這種情況下,會發現這些shared_ptr在析構函數中count的值始終大于0。
讓我們重新确認下上面的例子:
當ptr的析構函數被調用時,
·将引用計數減去1。
·然後檢查目前計數是否為0,但是是2,因為左側子元素和右側子元素都具有引用父項的shared_ptr對象,即ptr。
·隻有當ptr的記憶體被删除時,左右子節點才會被删除,但是由于引用計數大于0,這種情況不會發生。
·是以ptr和其子節點的記憶體都不會被删除。是以沒有析構函數被調用。
那麼,如何解決這個問題呢?
答案是使用 weak_ptr
Weak_ptr允許共享,但不擁有一個對象。 它的對象是由shared_ptr建立的。
std::shared_ptr<int> ptr = std::make_shared<int>(4);
std::weak_ptr<int> weakPtr(ptr);weak_ptr<int>
對于weak_ptr對象,我們不能直接使用運算符*和 - >來通路關聯的記憶體。首先,我們必須通過調用weak_ptr對象的的lock()函數來建立一個shared_ptr,這樣隻有我們可以使用它。
檢視如下的例子
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr = std::make_shared<int>(4);
std::weak_ptr<int> weakPtr(ptr);
std::shared_ptr<int> ptr_2 = weakPtr.lock();
if (ptr_2)
std::cout << (*ptr_2) << std::endl;
std::cout << "Reference Count :: " << ptr_2.use_count() << std::endl;
if (weakPtr.expired() == false)
std::cout << "Not expired yet" << std::endl;
return 0;
}
關鍵點:如果shared_ptr已經被删除,lock()會傳回空的shared_ptr
使用weak_ptr改進我們的二叉樹示例:
#include <iostream>
#include <memory>
class Node {
int value;
public:
std::shared_ptr<Node> leftPtr;
std::shared_ptr<Node> rightPtr;
//隻需要把shared_ptr改為weak_ptr;
std::weak_ptr<Node> parentPtr;
Node(int val) : value(val) {
std::cout << "Constructor" << std::endl;
}
~Node() {
std::cout << "Destructor" << std::endl;
}
};
int main() {
std::shared_ptr<Node> ptr = std::make_shared<Node>(4);
ptr->leftPtr = std::make_shared<Node>(2);
ptr->leftPtr->parentPtr = ptr;
ptr->rightPtr = std::make_shared<Node>(5);
ptr->rightPtr->parentPtr = ptr;
std::cout << "ptr reference count = " << ptr.use_count() << std::endl;
std::cout << "ptr->leftPtr reference count = " << ptr->leftPtr.use_count() << std::endl;
std::cout << "ptr->rightPtr reference count = " << ptr->rightPtr.use_count() << std::endl;
return 0;
}
輸出:
Constructor
Constructor
Constructor
ptr reference count = 1
ptr->leftPtr reference count = 1
ptr->rightPtr reference count = 1
Destructor
Destructor
Destructor