CMU15-445:Project #0 - C++ Primer

Project #0 - C++ Primer

本文是对CMU15-445课程第0个项目文档的一个粗略翻译和总结。仅供个人(M1kanN)复习使用。


1. Overview

本课程的所有编程项目都是在BusTub数据库管理系统上进行的,编程语言采用的是C++。本次项目是C++的一个热身项目。其中,C++的版本是C++17,但是知道C++11的知识点就足够了。

推荐书籍:C++ Primer, Effective Modern C++, A Tour of C++。笔者在做这个项目之前仅仅浏览了C++ Primer,并无C++项目经验。也是希望本次课能提高C++水平。

同时,本项目建议用GDB调试项目。也是对自身能力的一个提高。GDB参考网站:

2. Project Specification

本次项目要求实现一个由并发trie支持的key-value存储。Tries是一种高效的有序树数据结构,用于检索给定键的值。简单起见,我们将假设键是所有非空的可变长度的字符串,但实际上它们可以是任何任意类型。

Trie中的每个节点存储一个键的单个字符,可以有多个子节点代表不同的可能的下一个字符。当到达一个键的末尾时,一个标志被设置,以表明其对应的节点是一个结束节点。例子如下:![Trie](https://15445.courses.cs.cmu.edu/fall2022/project0/trie.svg)`<img src="https://15445.courses.cs.cmu.edu/fall2022/project0/graph.png" alt="Trie example" style="zoom: 79%;"暂略 />`

3. Implementation Guide

  1. InstructionSu我们需要编写的文件名是 p0_trie.h。(位置在 src/include/primer/p0_trie.h
    文件指定了函数原型和成员变量,我们只需要编写构造函数,析构函数和成员函数。我们可以添加任何额外的辅助函数和成员变量,但不要修改现有的函数和变量。

Task #1 - Templated Trie暂略

在头文件中,定义了3个我们需要编写的类。建议先编写单线程版本,再编写多线程版本。

TrieNode Class

TrieNode类定义了Trie树的单个结点。TrieNode有3个成员变量:

  • is_end_:表示是否是字符串的最后一个字符(也就是终结字符,也就是叶子结点!)
  • char:字符键值
  • children_:类型是 unordered_map<char, std::unique_ptr<TrieNode>>。注意保存的是 unique_ptr,智能指针。用来指向孩子结点。
    其中,InsertChildNodeGetChildNode都返回一个指向 unique_ptr的指针。这样做的原因是,我们就可以在不复制或者转移所有权的情况下访问 unique_ptr的数据。

最后,移动构造函数 TrieNode(TrieNode &&other_trie_node)用来将旧的TrieNode的智能指针转移到新的TrieNode上。请确保你没有在转移数据的时候复制智能指针!

TrieNodeWithValue Class

TrieNodeWithValue继承自 TrieNode,它表示一个路径的终结结点(结点中的 key_char是终结符)。该结点可以存放任意类型(T)的值。并且它的 is_end总Instruction是 true

当我们用一个给定的键遍历Trie树,并到达结束字符时,我们将根据不同情况来调用 TrieNodeWithValue的不同构造函数(详情见Trie类部分)

目前我们只需要知道 TrieNodeWithValue(char key_c暂略har, T value) 构造器用给定的关键字符和值,从头开始创建一个 TrieNodeWithValue。 (from scratch:从零开始)

TrieNodeWithValue(TrieNode &&trieNode, T value)构造函数从给定的TrieNode中获取 unique_ptrs的所有权,并将自己的 value_设置为给定的值TrieNodeWithValue(TrieNode &&trieNode, T value)。

Trie Class

Trie类定义一个实际的Trie树,支持插入,查找,移除操作。Trie树的根节点是所有键的开始,而且自己不应该存储任何字符值。

  • Insert
    要进行插入操作,我们首先需要用给定键值来遍历Tri暂略e树,如果 TrieNode不存在,就插入。注意,插入一个重复的值是不允许的,而且应该返回 false。一旦到达了终结结点,一共有3种可能:

    1. 这是一个不存在字符的 TrieNode
      这种情况,我们可以调用 TrieNodeWithValue(char key_char, T value)构造器来创建给定 key_charvalue的新节点。请确保一个指向 TrieNodeunique_ptr也能够存储一个指向 TrieNodeWithValueunique_ptr。(C++的多态性)
    2. 这是一个有字符的 TrieNode,但是不是一个终结结点。(is_end_ == false
      这意味这个 unique_ptr指向一个 TrieNode对象,而不是一个 TrieNodeWithValued对象。你需要调用 TrieNodeWithValue(TrieNode &&trieNode, T value) 构造器来转换。
    3. 这是一个有字符而且也是一个终结结点
      这意味着 unique_ptr指向 TrieNodeWithValue而且我们应该返回错误!因为我们不能插入重复内容。
  • Remove
    移除一个指定key的步骤:

    1. 基于给定key来遍历Trie树。若key不存在,立刻返回
    2. 将终结结点的 is_end_设为false。
    3. 若终结结点无任何孩子,将它从它父结点的 children_ map中删除。
    4. 向上遍历Trie树,递归地删除没有子节点的结点。当遇到一个结点有孩子的时候,停止。
  • GetValue:暂略
    给定一个key,返回一个类型T的对应值。若未找到,或者给定的类型不匹配,将 success设为false。
    注意:为了确认是否两个类型相同,对指向 TrieNode 的指针,调用强制类型转换 dynamic_cast ,转换为 指向 TrieNodeWithValue<T>的指针。若结果为 nullptr,则不匹配。

Task #2 - Concurrent Trie

我们需要确保插入、移除、取值操作在多线程环境下工作正常。我们可以使用`RwLatch`(BusTub的读写锁实现),或 `std::暂略shared_mutex`(C++STL库)去实现多线程。

本项目仅需要我们通过获取根节点的读写锁,来实现简单的多线程控制。`GetValue`函数应该获取在根节点上的读锁(通过调用 `RwLatch`的 `RLock方法`),`Insert`和 `Remove`操作需要获取在根节点的写锁。

如果我们用了`RWLatch`,请确保解锁了所有的锁,以避免死锁。

4. Instruction

注意!

本篇是在笔者完成实验后才写的一个记录性随笔。不负责步骤顺序的正确性!(可能有遗漏)
请不要完全参考这篇文章!!!!请去官网的Instruction一步一步跟着做!!!!

Creating Your Own Project Repository

笔者用的是Ubuntu22.4系统

  • 首先需要具备一定的git知识,并安装git。linux系统一般都有git。

  • 注册github账号,然后创建新仓库。并从官方的github上fork代码。注意一定要设为private仓库。

    $ git remote add \
        public https://github.com/cmu-db/bustub.git
    

    用下列代码来跟踪最新版代码:

    $ git fetch public
    $ git merge public/master
    

Setting Up Your Development Environment

  • Linux下:

    sudo build_support/packages.sh
    
  • 接着创建build文件夹。进行make操作

    $ mkdir build
    $ cd build
    $ cmake -DCMAKE_BUILD_TYPE=Debug ..
    $ make
    
  • 这里建议开启CMake的Debug模式,这样就既可以test也可以检查内存泄露

    $ cmake -DCMAKE_BUILD_TYPE=DEBUG ..
    
  • 要加速make可以用这个指令,加上多线程

    $ make -j$(nproc)
    

Testing

  • 写完代码就可以开始测试了:
    用来测试的文件在test/primer/starter_trie_test.cpp里面。把想测试的类,函数参数名字的前缀DISABLED_去掉,就可以指定测试了!

    $ cd build
    $ make starter_trie_test
    $ ./test/starter_trie_test
    
  • 我们 可以运行make check-tests命令来运行全部测试例子。由于我们才implement一个project,所以肯定会有很多fails。

Formatting

  • 本课程用的代码规范是:
    Google C++ Style Guide

  • 用的是 Clang 来自动化检查代码规范性。
    命令行输入:

    $ make format
    $ make check-lint
    $ make check-clang-tidy-p0
    

    来测试代码。其中make check-lint是用来检测细节的(也就是一些排版的错误)。make check-clang-tidy-p0可以检测用的语法有没有问题,有没有warning之类的。

Memory Leaks

  • 本课程用的是:LLVM Address Sanitizer (ASAN) and Leak Sanitizer (LSAN)
    开启Debug模式会自动检测。

  • 也可以用 Valgrind 来检测。
    运行:

    valgrind \
        --error-exitcode=1 \
        --leak-check=full \
        ./test/starter_trie_test
    
  • 这里笔者没有多此一举,直接用Debug模式就行了。后面遇到严重的问题再来试试看valgrind。

Development Hints

  • 建议不要用printf来debugging,用LOG_ *宏来调试更好!例子:

    LOG_INFO("# Pages: %d", num_pages);
    LOG_DEBUG("Fetching page %d", page_id);
    
  • 为了启用logging,我们应该加上-DCMAKE_BUILD_TYPE=Debug。也就是启用debug模式,才能用LOG。

  • 使用LOG的一些注意事项:

    The different logging levels are defined in src/include/common/logger.h. After enabling logging, the logging level defaults to LOG_LEVEL_INFO. Any logging method with a level that is equal to or higher than LOG_LEVEL_INFO (e.g., LOG_INFO, LOG_WARN, LOG_ERROR) will emit logging information. Note that you will need to add #include "common/logger.h" to any file in which you want to make use of the logging infrastructure.

  • 建议用gdb来调试。
    笔者后面会写一篇gdb的来学习。挖个坑。

Submission

5. Implementation Note

遇到的问题

  • moveforward的应用
    注意左值右值。以及是传递左值还是传递右值。
  • noexcept的运用
    一般只用在移动构造函数中。声明不产生except。
  • unique_ptr的使用
    • get()成员函数
      获得原来的指针
    • make_unique()
      多使用make_unique! 养成习惯
    • reset(q)
      将指针设为q。如果q 为空则是置为空。
    • 注意:
      由于unique_ptr的特性,我们经常使用一个指针来指向智能指针!!!这样就可以防止对unique_ptr的复制了
  • unordered_map的应用
    • erase()删除某个结点
  • 读写锁
    这里返回的时候要解锁就ok了

测试和提交

  • 语法测试的时候:
    注意不要改变代码的逻辑。不然就会前功尽弃了。

6. Summary

本次项目只是实现了一个Trie树。算是对课程项目的一个热身。不熟悉Cpp的同学也可以借此学习一些Cpp的知识。

7. Reference

[1] 字典树(Trie)

[2] CMU15-445 Project0 Trie

posted @ 2023-01-11 18:54  M1kanN  阅读(1336)  评论(0编辑  收藏  举报