CMU-15445 project0
一、代码解读
1.1 trie.h整体代码解读
#pragma once
#include <algorithm>
#include <cstddef>
#include <future> // NOLINT
#include <map>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
namespace bustub {
/// A special type that will block the move constructor and move assignment operator. Used in TrieStore tests.
class MoveBlocked {
public:
explicit MoveBlocked(std::future<int> wait) : wait_(std::move(wait)) {}
MoveBlocked(const MoveBlocked &) = delete;
MoveBlocked(MoveBlocked &&that) noexcept {
if (!that.waited_) {
that.wait_.get();
}
that.waited_ = waited_ = true;
}
auto operator=(const MoveBlocked &) -> MoveBlocked & = delete;
auto operator=(MoveBlocked &&that) noexcept -> MoveBlocked & {
if (!that.waited_) {
that.wait_.get();
}
that.waited_ = waited_ = true;
return *this;
}
bool waited_{false};
std::future<int> wait_;
};
// A TrieNode is a node in a Trie.
class TrieNode {
public:
// Create a TrieNode with no children.
TrieNode() = default;
// Create a TrieNode with some children.
explicit TrieNode(std::map<char, std::shared_ptr<const TrieNode>> children) : children_(std::move(children)) {}
virtual ~TrieNode() = default;
// Clone returns a copy of this TrieNode. If the TrieNode has a value, the value is copied. The return
// type of this function is a unique_ptr to a TrieNode.
//
// You cannot use the copy constructor to clone the node because it doesn't know whether a `TrieNode`
// contains a value or not.
//
// Note: if you want to convert `unique_ptr` into `shared_ptr`, you can use `std::shared_ptr<T>(std::move(ptr))`.
virtual auto Clone() const -> std::unique_ptr<TrieNode> { return std::make_unique<TrieNode>(children_); }
// A map of children, where the key is the next character in the key, and the value is the next TrieNode.
// You MUST store the children information in this structure. You are NOT allowed to remove the `const` from
// the structure.
std::map<char, std::shared_ptr<const TrieNode>> children_;
// Indicates if the node is the terminal node.
bool is_value_node_{false};
// You can add additional fields and methods here except storing children. But in general, you don't need to add extra
// fields to complete this project.
};
// A TrieNodeWithValue is a TrieNode that also has a value of type T associated with it.
template <class T>
class TrieNodeWithValue : public TrieNode {
public:
// Create a trie node with no children and a value.
explicit TrieNodeWithValue(std::shared_ptr<T> value) : value_(std::move(value)) { this->is_value_node_ = true; }
// Create a trie node with children and a value.
TrieNodeWithValue(std::map<char, std::shared_ptr<const TrieNode>> children, std::shared_ptr<T> value)
: TrieNode(std::move(children)), value_(std::move(value)) {
this->is_value_node_ = true;
}
// Override the Clone method to also clone the value.
//
// Note: if you want to convert `unique_ptr` into `shared_ptr`, you can use `std::shared_ptr<T>(std::move(ptr))`.
auto Clone() const -> std::unique_ptr<TrieNode> override {
return std::make_unique<TrieNodeWithValue<T>>(children_, value_);
}
// The value associated with this trie node.
std::shared_ptr<T> value_;
};
// A Trie is a data structure that maps strings to values of type T. All operations on a Trie should not
// modify the trie itself. It should reuse the existing nodes as much as possible, and create new nodes to
// represent the new trie.
//
// You are NOT allowed to remove any `const` in this project, or use `mutable` to bypass the const checks.
class Trie {
private:
// The root of the trie.
std::shared_ptr<const TrieNode> root_{nullptr};
// Create a new trie with the given root.
explicit Trie(std::shared_ptr<const TrieNode> root) : root_(std::move(root)) {}
public:
// Create an empty trie.
Trie() = default;
// Get the value associated with the given key.
// 1. If the key is not in the trie, return nullptr.
// 2. If the key is in the trie but the type is mismatched, return nullptr.
// 3. Otherwise, return the value.
template <class T>
auto Get(std::string_view key) const -> const T *;
// Put a new key-value pair into the trie. If the key already exists, overwrite the value.
// Returns the new trie.
template <class T>
auto Put(std::string_view key, T value) const -> Trie;
// Remove the key from the trie. If the key does not exist, return the original trie.
// Otherwise, returns the new trie.
auto Remove(std::string_view key) -> Trie;
// Get the root of the trie, should only be used in test cases.
auto GetRoot() const -> std::shared_ptr<const TrieNode> { return root_; }
};
} // namespace bustub
这段代码是一个用C++编写的头文件,它定义了一个名为bustub的命名空间中的几个类,这些类共同实现了一个Trie(前缀树或字典树)数据结构。下面是对这个头文件中的主要组成部分的概述:预处理指令和库包含:#pragma once: 这是一个常用的预处理指令,确保头文件内容只被包含(编译)一次,避免重复包含时的冲突。包含了一系列标准库头文件,如, ,
1.2 trie.h具体代码解释
因为自己开始c++基础比较薄弱,所以针对一些没有见过的代码格式进行了了解,下面是几个当时没理解的代码的相关论述:
1. auto Clone() const -> std::unique_ptr<TrieNode> override { return std::make_unique<TrieNodeWithValue<T>>(children_, value_); }
auto: 这是一个自动类型推断关键字。在这里,它用于表示函数的返回类型将在箭头(->)之后指定。
Clone(): 这是函数的名称。Clone函数通常用于创建对象的一个副本。
const: 这表明该函数不会修改类的任何成员变量,即它是一个只读函数。
-> std::unique_ptr: 这是尾置返回类型语法,用于明确指定函数的返回类型。在这里,函数返回一个std::unique_ptr,这是一个智能指针,指向TrieNode或其派生类的对象。
函数体:
return std::make_unique<TrieNodeWithValue>(children_, value_);: 这个语句创建并返回一个TrieNodeWithValue类型的对象的唯一指针。这个对象是当前对象的一个副本,因为它使用当前对象的children_和value_成员进行初始化。std::make_unique是C++14中引入的一个函数,用于创建一个唯一指针(unique_ptr),这是一种更安全、更简洁的方式来创建动态分配的对象。
2.template <class T>
用于定义一个类或函数,那么这个类或函数就可以适用于多种类型的T。当实例化这个模板时(即在代码中使用它时),T将被具体的类型替换,例如TrieNodeWithValue或TrieNodeWithValuestd::string,这取决于使用者指定的具体类型。
3.explicit TrieNodeWithValue(std::shared_ptr<T> value) : value_(std::move(value)) { this->is_value_node_ = true; }
explicit: 这个关键字用于构造函数,表示该构造函数禁止隐式类型转换。这意味着你不能通过赋值或其他隐式方式创建TrieNodeWithValue对象;必须直接调用构造函数。
std::move(value): 这里使用了std::move,将value参数转换为右值引用,允许在初始化value_时使用移动语义。这可以提高性能,尤其是对于资源密集型对象。
this->is_value_node_ = true;: 这个语句设置类的另一个成员变量is_value_node_为true。这可能表示当前节点是一个包含值的节点(即非仅用于结构的节点)。
4.TrieNodeWithValue(std::map<char, std::shared_ptr<const TrieNode>> children, std::shared_ptr<T> value) : TrieNode(std::move(children)), value_(std::move(value)) { this->is_value_node_ = true; }
std::map<char, std::shared_ptr> children: 第一个参数,表示这个Trie节点的子节点,其中键是字符类型,值是指向const TrieNode的共享指针。
std::shared_ptr value: 第二个参数,表示与这个Trie节点相关联的值,类型为T的共享指针。
1.3智能指针、dynamic_pointer_cast、以及const_pointer_cast
1.3.1智能指针
首先要知道什么是智能指针,智能指针的作用。
在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针(smart pointer)。智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。
这里主要涉及到shared_ptr:共享指针、unique_ptr:唯一指针,其他还包括两种weak_ptr和auto_ptr(C++17中已经被弃用),正好看到了智能指针,就对前三种指针进行一个概述:
**1.shared_ptr:**一种共享式智能指针,它可以被多个指针共享同一个资源,资源的引用计数会被自动管理,当所有的shared_ptr对象都不再需要该资源时,资源会自动被销毁。
**2.unique_ptr:**一种独占式智能指针,它确保了只有一个指针可以指向资源,可以通过std::move()函数将资源的所有权转移给其他unique_ptr对象。它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针对象,但是不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。
**3.weak_ptr:**一种弱引用智能指针,它可以被多个weak_ptr对象共享,但它不会增加资源的引用计数,当所有的shared_ptr对象都不再需要该资源时,weak_ptr对象会自动失效。弱引用智能指针std::weak_ptr可以看做是shared_ptr的助手,它不管理shared_ptr内部的指针。std::weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr中管理的资源是否存在。 通过调用std::weak_ptr类提供的expired()方法来判断观测的资源是否已经被释放;通过调用std::weak_ptr类提供的lock()方法来获取管理所监测资源的shared_ptr对象;通过调用std::weak_ptr类提供的reset()方法来清空对象,使其不监测任何资源。
1.3.2 dynamic_pointer_cast
1.std::dynamic_pointer_cast<目标类型>(被转换智能指针)
接受一个被转换的智能指针和目标类型的指针类型作为参数,并返回一个新的智能指针。dynamic_pointer_cast只适用于std::shared_ptr和std::weak_ptr类型的智能指针,用于进行智能指针的动态类型转换。
2.与dynamic_cast的区别
适用对象类型:dynamic_pointer_cast适用于智能指针类型,如std::shared_ptr和std::weak_ptr,用于进行智能指针的动态类型转换。而dynamic_cast适用于指针和引用类型,用于进行指针或引用类型的动态类型转换。
用法:dynamic_pointer_cast是一个函数模板,接受两个参数,分别是要转换的指针和目标类型的指针类型。它返回一个智能指针类型的结果。而dynamic_cast是一个运算符,在使用时需要使用dynamic_cast<目标类型>(被转换对象)的语法。它返回一个指针或引用类型的结果。
转换失败处理:dynamic_pointer_cast在转换失败时,会返回一个空智能指针(nullptr)。而dynamic_cast在转换失败时,如果转换是指针类型,会返回一个空指针(nullptr);如果转换是引用类型,会抛出std::bad_cast异常。
适用范围:dynamic_pointer_cast只适用于智能指针类型,因此只能用于具有动态多态性的对象。而dynamic_cast可以用于具有动态多态性的对象,也可以用于普通的指针和引用类型。
1.3.2 const_pointer_cast
这里与上面的dynamic_pointer_cast一样的规则,这里对几种常见的进行汇总:
1.const_cast
**主要用途:**对于常量指针,移除对象的常量性。
转换格式:const_cast < type-name > (expression)
要求:type-name和expression必须是同一种类型,他们只在const和volatile上存在区别,转换成功就赋值就行,转换失败则返回0。
volatile:提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
与常规类型转换的不同:常规类型转换可以转换为不同类型,
但是const_cast不允许不同类型的转换
基本原则:不允许将定义为const的变量转换为非const类型(这个是针对非指针)。
分几种情况:其实就是基本原则的体现
原来就是常量,用一个常量指针指向它,要将这个常量指针转换为非常量指针并修改其值,这是不允许的。
原来不是常量,用一个常量指针指向它,要将这个常量指针转换为非常量指针并修改其值,这是允许的。
2.static_cast
格式:static_cast < type-name > (expression)
当且仅当type-name类型可以隐式转换为expression类型,或反之,否则类型转换会报错,两者能互相转换
允许的转换
允许向上转换(继承类指针转换为基类指针)
允许向下转换(基类指针转换为继承类指针)
由于枚举类可以隐式转换为int,因此static_cast允许这类转换,并且允许将int类型转换为枚举类
允许将double转换为int,将int转换为double
允许将float转换为long,允许将long转换为float
不允许的转换
不同类型的指针之间互相转换
非指针类型和指针类型之间的相互转换
不同类型的引用之间的转换
3.reinterpret_cast
格式:reinterpret_cast < type-name > (expression)
转换时,执行的是逐个比特复制的操作。转换的安全性由程序员负责。
允许的转换:
可以将指针类型转换为能够容纳该指针类型的整型,反之不能转换。
不同类型的指针之间的转换
不同类型的引用之间的转换
不允许的转换:
不能将函数指针转换为数据指针
不允许的转换:
不能将函数指针转换为数据指针
1.4 具体project代码实现
1.4.1 task1
这个任务关键在于理解trie的结构,他最关键的在于需要对每个节点进行深拷贝,并且保证尽可能多的复用原来trie的节点,理解了trie的结构,也就大概能知道实现的思路。
1.4.1.1 GET方法(task1)
template <class T>
auto Trie::Get(std::string_view key) const -> const T * {
//从根节点开始
auto node= root_;
//沿着键中字符遍历Trie
for (char ch : key) {
//如果当前节点为nullptr,或者没有对应字符的子节点,返回nullptr
// 这意味着键不在Trie中
if (node == nullptr || node ->children_.find(ch) == node->children_.end()) {
return nullptr;
}
//移动到对应当前字符的子结点
node = node->children_.at(ch);
}
//检查最终节点是否为值节点并且类型匹配
// 如果是,尝试将节点转换为TrieNodeWithValue<T>类型
if (node != nullptr && node->is_value_node_) {
const auto *value_node = dynamic_cast<const TrieNodeWithValue<T>*>(node.get());
// 如果转换 成功并且节点包含正确类型的值,则返回该值的指针
if (value_node != nullptr) {
return value_node->value_.get();
}
}
// 如果键未找到或者类型不匹配,返回nullptr
return nullptr;
// You should walk through the trie to find the node corresponding to the key. If the node doesn't exist, return
// nullptr. After you find the node, you should use `dynamic_cast` to cast it to `const TrieNodeWithValue<T> *`. If
// dynamic_cast returns `nullptr`, it means the type of the value is mismatched, and you should return nullptr.
// Otherwise, return the value.
}
GET函数比较简单,只需要遍历,return正确值即可。
1.4.1.2 PUT方法(task1)
template <class T>
auto Trie::Put(std::string_view key, T value) const -> Trie {
std::shared_ptr<const TrieNode> grandparent = nullptr;
std::shared_ptr<const TrieNode> root_node = root_ ? root_->Clone() : nullptr;
std::shared_ptr<const TrieNode> parent = nullptr;
char ch = 0;
auto node =root_node;
//key=null
if (key.empty()) {
// 处理空字符串键
auto mutableNode = std::const_pointer_cast<TrieNode>(root_node);
auto valueNode = std::dynamic_pointer_cast<TrieNodeWithValue<T>>(mutableNode);
if (valueNode) {
// 如果根节点已经是一个值节点,更新其值
valueNode->value_ = std::make_shared<T>(std::move(value));
} else {
// 否则,创建一个新的值节点
auto newvalue = std::make_shared<TrieNodeWithValue<T>>(mutableNode->children_, std::make_shared<T>(std::move(value)));
root_node = newvalue;
}
return Trie(root_node);
}
// 遍历键中的每个字符
for (size_t i = 0; i < key.size(); ++i) {
ch = key[i];
if (node && node->children_.find(ch) != node->children_.end()) {
// 移动到对应的子节点
parent = node;
grandparent =node;
std::unique_ptr<TrieNode> cloned_node = node->children_.at(ch)->Clone();
node = std::shared_ptr<TrieNode>(std::move(cloned_node));
auto par = std::const_pointer_cast<TrieNode>(parent);
auto grand_par = std::const_pointer_cast<TrieNode>(grandparent);
par->children_[ch] = node;
grand_par->children_[ch] = node;
} else {
// 创建新节点
auto newNode = std::make_shared<const TrieNode>(std::map<char, std::shared_ptr<const TrieNode>>());
grandparent =node;
node = newNode;
auto mutableParent = std::const_pointer_cast<TrieNode>(parent);//非值节点
auto mutable_Parent = std::const_pointer_cast<TrieNode>(grandparent);//值节点
if (parent) {
// 更新父节点的子节点映射
if(mutableParent->is_value_node_ && mutable_Parent->is_value_node_) {
mutable_Parent->children_[ch] = newNode;
auto valueNode = std::dynamic_pointer_cast<const TrieNodeWithValue<T>>(mutable_Parent);
std::make_shared<const TrieNodeWithValue<T>>(mutable_Parent->children_, valueNode->value_);
}else if(mutableParent->is_value_node_){
mutableParent->children_[ch] = newNode;
auto valueNode = std::dynamic_pointer_cast<const TrieNodeWithValue<T>>(mutableParent);
std::make_shared<const TrieNodeWithValue<T>>(mutableParent->children_, valueNode->value_);
}else{
mutable_Parent->children_[ch] = newNode;
}
} else {
// 更新根节点
if (root_ == nullptr) {
// 根节点为空,创建新的根节点
auto new_Node = std::make_shared<const TrieNode>(std::map<char, std::shared_ptr<const TrieNode>>());
auto root_child = std::const_pointer_cast<TrieNode>(new_Node);
root_child->children_[ch] = newNode;
root_node = new_Node;
node = newNode;
parent = new_Node;
grandparent = new_Node;
} else {
// 根节点不为空,复制根节点以确保不影响现有节点
std::unique_ptr<TrieNode> cloned_node = root_->Clone();
auto newRoot = std::shared_ptr<TrieNode>(std::move(cloned_node));
newRoot->children_[ch] = newNode; // 添加新的映射
root_node = newRoot; // 设置新的根节点
}
}
parent =node;
}
}
// 在最终的节点处设置值
if (node) {
auto mutableNode = std::const_pointer_cast<TrieNode>(node);
if (mutableNode->is_value_node_) {
// 如果最后一个节点是值节点,更新其值
auto valueNode = std::dynamic_pointer_cast<TrieNodeWithValue<T>>(mutableNode);
if(valueNode){
valueNode->value_ = std::make_shared<T>(std::move(value));
} else{
auto newvalue = std::make_shared<TrieNodeWithValue<T>>(mutableNode->children_, std::make_shared<T>(std::move(value)));
std::const_pointer_cast<TrieNode>(grandparent)->children_[ch] = newvalue;
}
} else {
// 如果最后一个节点是非值节点,转换为值节点并赋值
auto newNodeWithValue = std::make_shared<TrieNodeWithValue<T>>(mutableNode->children_, std::make_shared<T>(std::move(value)));
if (grandparent) {
std::const_pointer_cast<TrieNode>(grandparent)->children_[ch] = newNodeWithValue;
} else {
root_node = newNodeWithValue;
}
}
}
return Trie(root_node);
}
PUT函数我觉得是最难的,因为要考虑值节点的与非值节点的变化,还有就是每一次的put都是在新的trie中进行,也就是要每遍历一个节点,都需要对该节点进行深拷贝,clone之后,也必须确保该节点与父节点的连接,另外对于空trie,第一次插入时,需要创造根节点等,思路不难,就是实现的时候,bug很多,需要不断的修改,加上c++能力薄弱,实现起来更加吃力。
1.4.1.3 REMOVE方法(task1)
auto Trie::Remove(std::string_view key) -> Trie {
// 辅助函数,用于递归地移除键
// 辅助函数,用于递归地移除键
std::shared_ptr<const TrieNode> grandparent = nullptr;
std::shared_ptr<const TrieNode> root_node = root_ ? root_->Clone() : nullptr;
std::shared_ptr<TrieNode> newRoot = std::const_pointer_cast<TrieNode>(root_node);
std::shared_ptr<const TrieNode> parent = nullptr;
auto node =root_node;
char ch = 0;
std::vector<std::pair<std::shared_ptr<TrieNode>, char>> path;
// 遍历键中的每个字符
for (size_t i = 0; i < key.size(); ++i) {
ch = key[i];
auto mutableNode = std::const_pointer_cast<TrieNode>(node);
auto it = node->children_.find(ch);
if (it != node->children_.end()) {
// 将当前节点和字符加入路径
path.emplace_back(mutableNode, ch);
parent = node;
std::unique_ptr<TrieNode> cloned_node = node->children_.at(ch)->Clone();
node = std::shared_ptr<TrieNode>(std::move(cloned_node));
auto parNode = std::const_pointer_cast<TrieNode>(parent);
parNode->children_[ch] = node;
} else {
// 如果当前字符不在子节点中,无法继续移除
return Trie(root_);
}
}
// 到达键的末尾,如果当前节点是值节点,移除该值节点
if (node->is_value_node_) {
auto mutableNode = std::const_pointer_cast<TrieNode>(node);
mutableNode->is_value_node_ = false;
node = std::make_shared<const TrieNode>(node->children_);
auto parNode = std::const_pointer_cast<TrieNode>(parent);
if(parent){
parNode->children_[ch] = node;
}else{
return Trie(node);
}
} else {
// 如果不是值节点,无法继续移除
return Trie(root_);
}
// 从路径中回溯并更新父节点的子节点
for (int i = path.size() - 1; i >= 0; --i) {
auto [innerParent, innerCh] = path[i];
auto child = innerParent->children_[innerCh];
if (!child->is_value_node_ && child->children_.empty()) {
// 如果子节点不是值节点且没有子节点,从父节点中移除该子节点
innerParent->children_.erase(innerCh);
} else {
// 否则,停止回溯
break;
}
}
if(newRoot->children_.empty()){
return Trie(nullptr);
}
return Trie(newRoot);
}
REMOVE方法实现逻辑也不难,需要注意的是,移除值节点后,要判断其后续以及父节点等是否为值节点,如果父节点不是值节点,需要同时移除这些非值节点。同时也需要进行clone深拷贝,在新的trie上进行操作。
!!!需要注意的是:这里的PUT和REMOVE函数,我第一反应是用递归更加方便,但是递归有个问题是,需要对每个节点进行深拷贝,同时要保证父节点与子结点的连接,但是因为新插入的节点可能与已有的值节点值类型不一样,导致在进行节点间映射的时候,导致无法正常进行,所以在这里我换成了非递归的形式。
1.4.2 task2:Concurrent Key-Value Store
在这里完全不用读写锁,只需要两把互斥锁,但得益于可持久化字典树每一次 Put、Remove 都会生成一个新版本的字典树,我们天然地解决了读写者的问题。因为每一个线程的 Put、Remove 都是对新树操作,完全不会影响到别的线程上的 Get,Get 仍在读旧版本的字典树。
class TrieStore {
public:
// This function returns a ValueGuard object that holds a reference to the value in the trie. If
// the key does not exist in the trie, it will return std::nullopt.
template <class T>
auto Get(std::string_view key) -> std::optional<ValueGuard<T>>;
// This function will insert the key-value pair into the trie. If the key already exists in the
// trie, it will overwrite the value.
template <class T>
void Put(std::string_view key, T value);
// This function will remove the key-value pair from the trie.
void Remove(std::string_view key);
private:
// This mutex protects the root. Every time you want to access the trie root or modify it, you
// will need to take this lock.
std::mutex root_lock_;
// This mutex sequences all writes operations and allows only one write operation at a time.
std::mutex write_lock_;
// Stores the current root for the trie.
Trie root_;
};
} // namespace bustub
这是该task的.h文件主要结构,这里的GET、PUT、REMPOVE具体操作都是调用task1中的具体函数,这里只需要对其进行相关加锁操作即可。
这个TrieStore 类就是对 Trie 的一个包装,含有两把锁,root_lock_ 用于 Get ,write_lock_ 用于 Put 和 Remove
1.4.2.1 GET
template <class T>
auto TrieStore::Get(std::string_view key) -> std::optional<ValueGuard<T>> {
// Pseudo-code:
// (1) Take the root lock, get the root, and release the root lock. Don't lookup the value in the
// trie while holding the root lock.
// (2) Lookup the value in the trie.
// (3) If the value is found, return a ValueGuard object that holds a reference to the value and the
// root. Otherwise, return std::nullopt.
std::shared_ptr<const TrieNode> root_copy;
auto trie = Trie();
{
// 使用互斥锁来保护根节点的读取
std::lock_guard<std::mutex> lock(root_lock_);
trie = root_;; // 获取Trie的根节点
}
// 在Trie中查找键
const T* value = trie.Get<T>(key); // 假设TrieNode有Get方法
// 根据查找结果返回
if (value) {
return ValueGuard<T>(trie, *value);
} else {
return std::nullopt;
}
}
因为读并不会修改 Trie,Get 时没有必要上读锁,只需要用root_lock_即可。
1.4.2.1 GET
template <class T>
void TrieStore::Put(std::string_view key, T value) {
std::shared_ptr<const TrieNode> root_copy;
std::lock_guard<std::mutex> lock(write_lock_);
// 复制当前根节点,对其进行修改
Trie new_trie = root_.Put<T>(key, std::move(value)); // 假设Trie有Put方法
{
std::lock_guard<std::mutex> root_lock(root_lock_);
root_ = new_trie;
}
// You will need to ensure there is only one writer at a time. Think of how you can achieve this.
// The logic should be somehow similar to `TrieStore::Get`.
}
put函数需要对其进行写加锁。
1.4.2.3 REMOVE
void TrieStore::Remove(std::string_view key) {
// You will need to ensure there is only one writer at a time. Think of how you can achieve this.
// The logic should be somehow similar to `TrieStore::Get`.
std::lock_guard<std::mutex> lock(write_lock_); // 获取写锁
// 执行删除操作并获取新的 Trie 实例
Trie new_trie_version = root_.Remove(key); // 假设 Trie 有 Remove 方法
// 更新 Trie 的根节点
{
std::lock_guard<std::mutex> root_lock(root_lock_);
root_ = new_trie_version;
}
}
同样,因为涉及到对trie的修改,所以需要对其加写锁。