P0 C++Primer

文章目录

    • Task1 写时复制的字典树
    • Task2 并发键值存储

Task1 写时复制的字典树

class TrieNode {
    public:
    // Create a TrieNode with no children.
    TrieNode() = default;//声明默认构造器,等效于TrieNod(){},C++11特性
    ...
}
explicit TrieNode(std::map<char, std::shared_ptr<const TrieNode>> children) : children_(std::move(children)) {}

explicit关键字用于阻止C++进行隐式类型转换,强制要求传入参数的类型必须符合声明。

函数(类型 参数名):属性成员名(参数名){}等价于函数(类型 参数名){this.属性成员名=参数名;}

std::move()将对象的状态或所有权转换给另一对象,这里是调用构造函数时传入的对象childrenchildren_,之后传入的对象就为空了,这样做只是转移,没有内存的搬迁或者内存拷贝,如果不加则需要先将传入对象拷贝一份为临时对象,然后给属性成员赋值,之后销毁临时对象。因此,通过std::move(),可以避免不必要的拷贝操作。这样的构造函数称为移动构造函数。

std::shared_ptr智能指针之一,共享指针类,实例化后使用同普通指针,有一些成员函数,有引用计数能力,有一个智能指针指向一块内存,同样指向该内存的关联指针的引用计数加一,反之减一,当计数为0时最后一个智能指针使用delete函数删除内存。

virtual auto Clone() const -> std::unique_ptr<TrieNode> { return std::make_unique<TrieNode>(children_); }

函数() const {...}作用是标识成员函数隐式传入的参数this为常量,这样该成员函数就不允许修改当前类的属性了

auto 函数() -> 类型{...}作用是推导出函数的返回类型

std::unique_ptr智能指针之一,唯一指针类,拥有管理内存的所有权,没有拷贝构造函数,只有移动构造函数,不能多个unique_ptr对象共享一段内存

std::make_unique<>()尽可能避免将裸指针传递给一个std::unique_ptr的构造函数,常用的替代手法是使用std::make_unique<>()std::make_shared同理

auto Trie::Get(std::string_view key) const -> const T * {

C++中与字符串有两种风格,分别是C风格的字符串、std::string字符串。C风格的字符串性能更高,但是也不方便操作使用。C++17中我们可以使用std::string_view来获取一个字符串的视图,字符串视图并不真正的创建或者拷贝字符串,只是记录了自己对应的字符串的指针和偏移位置,拥有一个字符串的查看功能,性能比std::string高很多

std::map<char, std::shared_ptr<const TrieNode>> children_;

该字典树中结点TrieNode本质是一个map,key是键中的下一个字符,value是对应的子节点,对于叶子节点,有TrieNodeWithValue类继承TrieNode,多了个std::shared_ptr value_;

return std::make_shared<TrieNodeWithValue<T>>(std::make_shared<T>(std::forward<T>(value)));

std::forward通常是用于完美转发的,它会将输入的参数原封不动地传递到下一个函数中,这个“原封不动”指的是,如果输入的参数是左值,那么传递给下一个函数的参数的也是左值;如果输入的参数是右值,那么传递给下一个函数的参数的也是右值。

引用折叠:形如int &&aint &a的参数

下文取自https://www.zhihu.com/question/40346748/answer/2971112947

void func(int &&a/*右值引用*/, int &b/*左值引用*/){}
//左值引用要传入左值,右值引用同理

template<typename T>
void fun(T &&t/*万能引用*/){}//可以传左值或右值

//但是万能引用如果传入左值引用则报错
int a = 1;
int &b = a;//b为左值引用

编译器允许在一定的情况下进行隐含的多层引用推导,这就是 reference collapsing (引用折叠)。C++中有两种引用(左值引用和右值引用),因此引用折叠就有四种组合。引用折叠的规则:

如果两个引用中至少其中一个引用是左值引用,那么折叠结果就是左值引用;否则折叠结果就是右值引用。

using T = int &;
T& r1;  // int& & r1 -> int& r1
T&& r2; // int& && r2 -> int& r2

using U = int &&;
U& r3;  // int&& & r3 -> int& r3
U&& r4; // int&& && r4 -> int&& r4

//可以把单个&当做1,&&当做0,做一个OR运算
using T = int &;
T& r1;  // 相当于1 OR 1,结果为1,即int& & r1 -> int& r1
T&& r2; // 相当于1 OR 1,结果为1,即int& && r2 -> int& r2

using U = int &&;
U& r3;  // 相当于0 OR 1,结果为1,即int&& & r3 -> int& r3
U&& r4; // 相当于0 OR 0,结果为0,即int&& && r4 -> int&& r4

Task2 并发键值存储

// 这个函数返回一个ValueGuard对象,该对象保存着对trie中值的引用。
// 如果键不存在于树中,它将返回std::nullopt。
template <class T>
auto Get(std::string_view key) -> std::optional<ValueGuard<T>>;

std::optional可以隐式转换为boolean类型来表示当前是否有有效值,同时optional支持操作符*来进行取值。从这个例子中我们可以看出来,用optional作为函数返回值可以更好地解决引言中的问题,函数更简洁同时接口含义也更明确。

trie_store.cpp实现TrieStore类,该类属性:

private:
// 这个互斥锁保护根节点。每次要访问或修改树的根目录时,都需要使用此锁。
std::mutex root_lock_;

// 这个互斥锁对所有写操作进行排序,一次只允许一个写操作。
std::mutex write_lock_;

// Stores the current root for the trie.
Trie root_;

互斥量std::mutex可直接使用root_lock_.lock()root_lock_.unlock()

/// 一个特殊类型,它将阻塞move构造函数和move赋值操作符。用于TrieStore测试。
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_;
};

std::future用于异步处理,std::async异步地执行传入的函数并返回std::future,执行结束前使用std::futrue.get()可以尝试获取执行结果,该方法会阻塞当前线程直到结果可用。

noexcept编译期完成声明和检查工作.noexcept 主要是解决的问题是减少运行时开销. 运行时开销指的是, 编译器需要为代码生成一些额外的代码用来包裹原始代码,当出现异常时可以抛出一些相关的堆栈stack unwinding错误信息, 这里面包含,错误位置, 错误原因, 调用顺序和层级路径等信息.当使用noexcept声明一个函数不会抛出异常候, 编译器就不会去生成这些额外的代码, 直接的减小的生成文件的大小, 间接的优化了程序运行效率.

c++ 中的关键字noexcept - 知乎 (zhihu.com)

你可能感兴趣的:(BusTub项目作业源码阅读,c++,sql,数据结构)