[英]Circular double linked list with smart pointers in c++
是否可以在 C++ 中使用智能指針創建一個循環雙向鏈表
struct Node {
int val;
shared_ptr<Node> next;
weak_ptr prev;
};
shared_ptr<Node> head;
但這將具有共享指針的循環引用,因此無法正確解除分配。
使循環鏈表本身成為一個類(使用您需要的任何操作來構建它,例如附加)。 通過設置 tail->next = nullptr 使其析構函數斷開鏈接。 您斷開哪個鏈接無關緊要,因此如果您不使用頭和尾,只需將其中任何一個設置為 nullptr,就可以了。
在我的測試中,我做了一個循環鏈表,節點沒有破壞。 然后在最后,我在它退出之前添加了 tail->next = nullptr,並且所有的析構函數都正確地觸發了。
我最初發布的答案在細節上相當簡單。 這個給出了如何在沒有內存泄漏的情況下實現循環鏈表並仍然遵守零規則的正確解釋。 答案基本相同,使用哨兵,但機制比我最初提出的要復雜一些。
訣竅是使用一個哨兵類型,它的行為就像一個列表節點,但實際上並沒有真正具有指向列表頭部的共享指針。 為此,節點類應分為行為對象和狀態對象。
class NodeState {
std::shared_ptr<Node> next_;
std::weak_ptr<Node> prev_;
int value_;
NodeState (int v) : value_(v) {}
NodeState (std::shared_ptr<Node> p) : next_(p), prev_(p) {}
//...
};
class Node {
virtual ~Node () = default;
virtual NodeState & state () = 0;
std::shared_ptr<Node> & next () { return state().next_; }
std::weak_ptr<Node> & prev () { return state().prev_; }
int & value () { return state().value_; }
void insert (const std::shared_ptr<Node> &p) {
//...
}
};
現在,您可以定義一個節點實現和一個哨兵實現。
class NodeImplementation : public Node {
NodeState state_;
NodeState & state () { return state_; }
NodeImplementation (int v) : state_(v) {}
//...
};
class NodeSentinel : public Node {
List &list_;
NodeSentinel (List &l) : list_(l) {}
NodeState & state () { return list_.sentinel_state_; }
};
列表本身包含一個由哨兵對象使用的NodeState
。 初始化時,列表創建一個哨兵對象並初始化其狀態。
class List {
//...
NodeState sentinel_state_;
std::shared_ptr<Node> head () { return sentinel_state_.next_; }
std::shared_ptr<Node> sentinel () {
return std::shared_ptr<Node>(head()->prev());
}
//...
public:
List () : sentinel_state_(std::make_shared<NodeSentinel>(*this)) {}
//...
void push_front (int value) {
head()->insert(std::make_shared<NodeImplementation>(value));
}
void push_back (int value) {
sentinel()->insert(std::make_shared<NodeImplementation>(value));
}
//...
};
那么,這個組織是做什么的呢? 它通過使用哨兵節點作為中斷來避免循環引用的問題。 雖然列表的尾部指向哨兵對象,但哨兵對象本身並不指向任何東西。 相反,它使用列表本身的狀態來確定其下一個和前一個鄰居。
因此,循環共享指針僅在列表存在時才持續存在。 一旦列表被銷毀, Item A
失去其引用,並且通過多米諾骨牌效應, Sentinel
本身將被銷毀。
一個基本點是哨兵對象本身絕不能直接暴露給列表界面的用戶。 它應該始終保持在列表對象的內部。 它本質上表示類似 STL 的容器中的end()
,並且從邏輯上講,它永遠不能從列表中刪除(直到列表本身被銷毀)。 實際上,這意味着如果傳入的迭代器代表哨兵,則列表上的刪除操作需要提前退出。
也可以定義一個成員函數 next() ,它可以在共享指針或弱指針之間進行選擇。
#include <iostream>
#include <memory>
using namespace std;
struct T {
int n_;
shared_ptr<T> next_;
weak_ptr<T> weaknext_;
T(shared_ptr<T> next, int n) : next_(next), n_(n) {};
auto next() {
if (next_ == nullptr)
return shared_ptr<T>(weaknext_);
return next_;
}
~T() { cout << n_ << "ok\n"; }
};
int main() {
auto p0 = make_shared<T>(nullptr, 1);
auto p1 = make_shared<T>(p0, 2);
auto p2 = make_shared<T>(p1, 3);
p0->weaknext_ = p2; //makes the list circular
auto p = p2;
for (int i = 0; i < 5; ++i) {
cout << p->n_ << "\n";
p = p->next();
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.