0. 实验结果
1. 环境配置
1.1 虚拟机Ubuntu 22.04.4 LTS
1.2 Vscode 插件安装clangd:代码补全
1.3 参考官方Project 0完成环境配置
1.4 获取到的代码,切换分支到2023 Fall
git checkout fc57dab // 仓库中2023 Fall release版本的哈希值
git branch dev
git checkout dev // 切换到自己的分支
1.5 修改bustub-private根目录下CMakeLists.txt,增加以下,以使用clang编译
set(CMAKE_C_COMPILER "/usr/bin/clang")
set(CMAKE_CXX_COMPILER "/usr/bin/clang++")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
如图所示:
同一个CMakeLists.txt文件下注释enable_testing()
,因为我不想它编译后自动执行,有时候代码有问题,编译成功之后,我希望手动gdb xx.out
进行调试。
1.6 Ubuntu中创建共享文件夹(方便本地访问,将打包后的提交代码,直接拖拽到提交网站里)
参考Ubuntu Samba映射共享文件夹
如果遇到问题参考连接问题解决
具体流程:
1.6.1 编辑sudo gedit /etc/samba/smb.conf
,末尾添加
[bustub]
comment = bustub-private
path = /home/shuiyihang/bustub-private
available = yes
browseable = yes
public = yes
read only = no
guest ok = yes
create mask = 0777
valid users = shuiyihang
1.6.2 添加用户密码
sudo smbpasswd -a shuiyihang
1.6.3 重启服务
sudo systemctl restart smbd
1.6.4 本地windows主机执行或者win+r,输入\\虚拟机IP\bustub
net use * /del /y
net use Z: \\虚拟机ip\bustub /user:用户 密码
2. 任务总结
2.1 Trie::Put 任务
遍历字符串键值的每个字符,可能的情况:
2.1.1
字符串遍历结束时候,每一个字符都可以在map容器中找到对应的节点,此时需要创建TrieNodeWithValue
类型的节点,并继承原来TrieNode
节点的所有孩子。
2.1.2
字符串遍历过程中,无法在map容器中找到节点,此时需要创建TrieNode
节点,并继续遍历,知道字符串遍历结束,创建TrieNodeWithValue
节点,存入值。
处理特殊情况:
2.1.3
空键值的插入
2.1.4
空trie的插入
PS补充:
在这个任务中,我看很多人都是使用的栈——自底向上的构建,其实不然,自上向下也是可行的(我正是这种方法),原因在于通过Clone
得到的是可以修改的节点。这两种方法并无区别,因为两种方法都是将遍历字符过程中碰到的节点进行Clone。一定要注意不能修改原trie,不然并发测试,肯定无法通过。
2.2 Trie::Remove 任务
2.2.1
删除的节点是叶子节点,删除后要考虑父节点是否还有别的叶子节点或者父节点本身携带数据,是一个TrieNodeWithValue
节点,这两种情况下,父节点都不能删除,而是应该克隆之后,删除map容器中的键。
2.2.2
删除的节点如果是中间节点,也即它有孩子,此时应该创建一个TrieNode
节点,并继承原来节点的所有孩子。
2.2.3
遍历字符串中的字符,无法找到对应键值,直接返回原来的trie,表示无需删除。
这个任务我是使用的栈结构,将访问到的所有节点压入栈中,从最后一个节点进行修改,向上修改所有节点,返回传入新的根节点创建的trie。
2.3 TrieStore::Put 任务
2.4 TrieStore::Remove 任务
这两个任务合起来一起说,因为都涉及到树的修改。
通过调用上面实现的Trie接口完成这两个接口。
注意trie_store.h
中关于两个锁的注释提示。root_lock_
用于root的读和写,所有读写操作一定要上锁.
使用方式如下
root_lock_.lock();
// 操作
root_lock_.unlock();
2.5 Debugging任务
这里我选择的方式是在trie_debug_test.cpp
中添加代码,打印或者通过gdb来查看变量值获得。
2.6 SQL String Functions任务
这个实现很简单,但是无法像引导里面所说进行测试,因为BufferPoolManager is not implemented yet
还没有实现。
这里我编写完成之后并没有本地测试,而是后面提交gradescope完成的测试。
一定要注意的点:
不能修改树,否则并发控制会出问题
3. 知识总结
3.1 std::move(移动语义)
适用于不再需要 value
的情况下,希望把它的资源转移给另一个对象,避免不必要的复制开销。
调用move,转换为一个右值引用,表示它的资源可以被移动。
在本任务中,存放value,一定要使用,因为自定义类型MoveBlocked
拷贝构造函数被显式删除,所以不能通过值传递的方式。
MoveBlocked(const MoveBlocked &) = delete;
3.2 dynamic_cast
执行安全类型转换
将基类指针或引用转换为派生类指针或引用
它在有多态性(基类必须有虚函数)的情况下使用,如果类型不正确,指针会返回 nullptr
在进行智能指针转换时,需要先使用智能指针的get
方法后,再进行转换。
3.3 std::dynamic_pointer_cast
专门用于智能指针之间的类型转换,适用于 std::shared_ptr
目标类型智能指针 = std::dynamic_pointer_cast<目标类型>(基类智能指针);
3.4 容器 map
操作
emplace: 在容器中插入元素,与insert相比,不需要先构造,再插入。避免不必要的拷贝和移动操作。
dePtr->children_.insert(std::pair<char, int>('a',10));
dePtr->children_.emplace('a',10);
需要注意:
如果键已经存在,emplace
不会插入新的元素,而是返回一个pair,pair的第一个元素是一个迭代器指向已存在的元素,第二个元素是一个bool值,表示插入操作是否成功,成功true,失败false。
erase: 传入键值,删除元素。
find: 查找某个键值,成功返回元素的迭代器,否则返回容器end()
at: 和[]值访问一样,访问键对应的值
[]: 可以用于访问和修改
3.5 noexcept
先解释下:移动构造函数
第一个参数是一个右值引用。
CLTest(CLTest &&s) noexcept{}
接下来解释noexcept:
用于指定函数是否可能抛出异常,在函数的参数列表后,使用 noexcept
,可以向编译器和调用者表明函数不会抛出异常,从而允许编译器进行更多的优化。
不抛出异常的移动构造函数和移动赋值运算符必须标记为noexcept
,以保证对自定义类型的对象进行移动而不是拷贝。
3.6 std::thread 线程的使用
std::thread
的基本用法是创建一个线程对象,并传递一个可调用对象(如函数、函数对象、Lambda 表达式等)作为线程的入口点
调用join
方法,等待线程结束。join
方法会阻塞主线程,直到线程执行完毕。
std::thread t1([]{
std::cout << "hello thread" << std::endl;
});
t1.join();
3.7 lambda表达式
C++11引入的匿名函数语法,基本语法
[捕获列表](参数列表) -> 返回类型 { 函数体 }
-
捕获列表:用于捕获外部变量,可以是值捕获、引用捕获或混合捕获。
-
参数列表:Lambda 函数的参数列表,类似于普通函数的参数列表,可以没有
-
返回类型:Lambda 函数的返回类型,可以省略,编译器会自动推导。
-
函数体:Lambda 函数的具体实现。
3.8 std::future
用于异步编程,允许你在一个线程中启动一个异步任务,并在另一个线程中获取该任务的结果。
std::future
的基本用法是与 std::async
一起使用。std::async
启动一个异步任务,并返回一个 std::future
对象。
// 使用 std::async 启动一个异步任务
std::future<int> futureResult = std::async(std::launch::async,asyncFunction,参数);
std::cout << "等待异步任务完成..." << std::endl;
// 等待异步任务完成并获取结果
int result = futureResult.get();
std::cout << "异步任务完成,结果是: " << result << std::endl;
PS补充: Clone的细节
注意到Clone的时候使用到的是TrieNode的有参构造,所以会先产生一份children的副本,有参构造的函数体中,对children使用了move操作,这个操作不会转移旧trie中children的所有权,只是转移副本的所有权。
4.提交流程
访问 gradescope,注册账号,学校选择CMU(全称),课程编号KK5DVJ
,第一节课中获取到的。
4.1 检查错误和编写规范
make format
make check-clang-tidy-p0
4.2 打包
make submit-p0
4.3 签署学术规范声明(必须通过,生成md文件,自动放入打包后的文件)
python gradescope_sign.py
提交的内容,只有我们修改的文件: