在线测试需要使用 Gradescope,使用课程提供的课程码
PXWVR5
,学校填
Carnegie Mellon University
,Student ID 不需要。
既然这门课用的 C++,那么就需要考察学生的 C++ 水平。CMU 15-445
的要求:C++17, GDB。
鉴于课程使用的工具,最好在 Linux 下做此课程实验。
首先需要 clone 一份 CMU 的 bustub(需要去 Release
手动下载 Fall2022 版本,或者手动 reset 到对应的 commit
d830931a9b2aca66c0589de67b5d7a5fd2c87a79
,否则内容对应不上)。
Task #1 - Templated Trie
在原有模板上实现一颗 Trie 树。
没什么好说的,只要知道 Trie
树是个什么东西就行了,直接模拟,总共就几十行代码。
在写的时候可能遇到的主要问题:不会用智能指针等现代 C++
内容,只能边查边写,多用用就会了。
(不过我一直感觉 C++ 的智能指针用着怪难受的,Rust 写着就挺舒服)
似乎在 C++17 后 Clang
会提示将函数写为类型后置的形式,虽然感觉完全没必要。。
int func() {}
→
auto func() -> int {}
Task #2 - Concurrent Trie
在 Task #1 的基础上通过加锁实现并发。
因为项目本身已经提供了一个 RWLatch
(std::shared_mutex
),可以直接用。
具体实现只需要在函数开始的时候获取锁,离开时释放即可。因为读操作可以同时进行,需要注意不同操作是使用
.RLock()
还是 .WLock()
。
写完之后测试发现,这个 OJ
提示信息基本等于没有,基本没法调试,于是可能会用到提供的本地测试,注意需要把相应测试的
TEST(StarterTest, DISABLED_xxx)
改成
TEST(StarterTest, xxx)
,否则会忽略掉
测试的时候发现有大量的内存泄露,发现是智能指针使用不当造成的
其中有一段如下的代码:
1
| *position = std::make_unique<TrieNode>(std::move(*position->release()));
|
其中 position
是一个
std::unique_ptr<TrieNode>
,因为
release()
后没有释放对应指针的值,于是造成内存泄露。需要改成:
1 2 3
| auto ptr = position->release(); *position = std::make_unique<TrieNode>(std::move(*ptr)); delete ptr;
|
另外自己在 DEBUG
的时候犯了个蠢,子类需要重载析构函数,否则也会内存泄漏(这不废话)
最终代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
| #pragma once
#include <memory> #include <stack> #include <stdexcept> #include <string> #include <unordered_map> #include <utility> #include <vector>
#include "common/exception.h" #include "common/rwlatch.h"
namespace bustub { class TrieNode { public:
explicit TrieNode(char key_char) : key_char_(key_char) {}
TrieNode(TrieNode &&other_trie_node) noexcept : key_char_(other_trie_node.key_char_), children_(std::move(other_trie_node.children_)) {}
auto HasChild(char key_char) const -> bool { return children_.count(key_char) != 0; }
auto HasChildren() const -> bool { return !children_.empty(); }
auto IsEndNode() const -> bool { return is_end_; }
auto GetKeyChar() const -> char { return key_char_; }
auto InsertChildNode(char key_char, std::unique_ptr<TrieNode> &&child) -> std::unique_ptr<TrieNode> * { if (HasChild(key_char) || child->GetKeyChar() != key_char) { return nullptr; } return &(children_[key_char] = std::move(child)); }
auto GetChildNode(char key_char) -> std::unique_ptr<TrieNode> * { if (!HasChild(key_char)) { return nullptr; } return &children_[key_char]; }
void RemoveChildNode(char key_char) { children_.erase(key_char); }
void SetEndNode(bool is_end) { is_end_ = is_end; }
protected: char key_char_; bool is_end_{false}; std::unordered_map<char, std::unique_ptr<TrieNode>> children_; };
template <typename T> class TrieNodeWithValue : public TrieNode { private: T value_;
public:
TrieNodeWithValue(TrieNode &&trieNode, T value) : TrieNode(std::move(trieNode)), value_(value) { SetEndNode(true); }
TrieNodeWithValue(char key_char, T value) : TrieNode(key_char), value_(value) { SetEndNode(true); }
~TrieNodeWithValue() override = default;
auto GetValue() const -> T { return value_; } };
class Trie { private: std::unique_ptr<TrieNode> root_; ReaderWriterLatch latch_;
public: Trie() { root_ = std::make_unique<TrieNode>('\0'); }
template <typename T> auto Insert(const std::string &key, T value) -> bool { if (key.empty()) { return false; } latch_.WLock(); auto position = &root_; for (const auto c : key) { if (!position->get()->HasChild(c)) { position->get()->InsertChildNode(c, std::make_unique<TrieNode>(c)); } position = position->get()->GetChildNode(c); } if (position->get()->IsEndNode()) { latch_.WUnlock(); return false; } if (dynamic_cast<TrieNodeWithValue<T> *>(position->get()) == nullptr) { auto ptr = position->release(); *position = std::make_unique<TrieNodeWithValue<T>>(std::move(*ptr), value); delete ptr; } latch_.WUnlock(); return true; }
auto Remove(const std::string &key) -> bool { if (key.empty()) { return false; } latch_.WLock(); auto position = &root_; std::stack<std::unique_ptr<TrieNode> *> trace; trace.push(position); for (const auto c : key) { if (!position->get()->HasChild(c)) { latch_.WUnlock(); return false; } position = position->get()->GetChildNode(c); trace.push(position); } if (!position->get()->IsEndNode()) { latch_.WUnlock(); return false; } auto ptr = position->release(); *position = std::make_unique<TrieNode>(std::move(*ptr)); delete ptr; while (trace.size() > 1) { auto p = trace.top()->get(); if (p->HasChildren() || p->IsEndNode()) { break; } auto child_char = p->GetKeyChar(); trace.pop(); trace.top()->get()->RemoveChildNode(child_char); } latch_.WUnlock(); return true; }
template <typename T> auto GetValue(const std::string &key, bool *success) -> T { if (key.empty()) { *success = false; return {}; } latch_.RLock(); auto position = &root_; for (const char c : key) { if (!position->get()->HasChild(c)) { *success = false; latch_.RUnlock(); return {}; } position = position->get()->GetChildNode(c); } if (!position->get()->IsEndNode()) { *success = false; latch_.RUnlock(); return {}; } TrieNodeWithValue<T> *ptr = dynamic_cast<TrieNodeWithValue<T> *>(position->get()); if (!ptr) { *success = false; latch_.RUnlock(); return {}; } *success = true; latch_.RUnlock(); return ptr->GetValue(); } }; }
|