C++并发与实战(2):trie.cpp实现

2. trie.cpp实现

注意到trie.h给了我们三个接口

auto Get(std::string_view key) const -> const T *;
template <class T>
auto Put(std::string_view key, T value) const -> Trie;
auto Remove(std::string_view key) const -> Trie;

我们就要在trie.cpp下面实现这三个接口

实现前的注意点

  • 由于每次修改操作后,都需要返回一个新的trie实例,所以所有操作都是 const 的,不能修改原有的 Trie,需要通过创建新节点来实现修改操作
  • 要充分利用 std::shared_ptr 来共享不变的节点

区别于传统的std::string,**std::string_view又有什么不同呢?

std::string_view

  • std::string_view 是 C++17 引入的一个轻量级的字符串视图类,它并不拥有字符串数据,而只是一个指向字符串数据的指针和长度。它可以避免不必要的字符串复制,提升性能。
  • 它的主要作用是将 std::stringchar[] 类型的数据以只读方式传递给函数,而不会进行拷贝。
    在此代码中,key 是传入的字符串,类型是 std::string_view,表示我们只关心传入字符串的内容,而不关心它的内存位置。

2.1 GET方法

get方法是trivial的,遍历一遍即可(注意特判为空节点的情况,不然你就和我一样段错误)
具体的实现如下:

template <class T>
auto Trie::Get(std::string_view key) const -> const T * {
    auto curr = root_;
    // 遍历key中的每个字符
    for (char c : key) {
        if (curr == nullptr || curr->children_.find(c) == curr->children_.end()) {
            return nullptr;  // 找不到对应的节点,返回nullptr
        }
        curr = curr->children_.at(c);
    }

    // 找到最后一个节点后,检查是否是值节点且类型匹配
    if (curr == nullptr || !curr->is_value_node_) {
        return nullptr;
    }

    // 尝试将节点转换为带值节点
    const auto *value_node = dynamic_cast<const TrieNodeWithValue<T> *>(curr.get());
    if (value_node == nullptr) {
        return nullptr;  // 类型不匹配
    }

    return value_node->value_.get();
}

我们的节点有两种:一种是普通的trienode节点,一种是带值的trienodewithvalue节点(叶节点,每次插入的字符串最后的新增节点),我们需要判断当前字符串是否存在于trie当中,就要判断最后走到的节点是不是带值的节点,但是cpp里无法直接compare decltype(a)和decltype(b),那么怎么办呢?

dynamic_cast

dynamic_cast 主要用于 安全地进行运行时类型转换,适用于 多态类型(即至少有一个虚函数的类)。它的主要用途如下:

1. 向下转换(Downcasting)

将基类指针/引用转换为派生类指针/引用,并确保转换的合法性。如果转换失败(对象不是该派生类),dynamic_cast 返回 nullptr(指针情况)或抛出 std::bad_cast 异常(引用情况)。

#include <iostream>
class Base { public: virtual ~Base() {} };  // 需要至少有一个虚函数
class Derived : public Base { public: void show() { std::cout << "Derived\n"; } };

int main() {
    Base* base = new Derived();  // 向上转换(Upcasting),安全
    Derived* derived = dynamic_cast<Derived*>(base);  // 向下转换(Downcasting)
    
    if (derived) derived->show();  // 转换成功
    else std::cout << "Conversion failed\n";

    delete base;
}

2. 验证对象类型(Run-Time Type Identification, RTTI)

通过 dynamic_cast 判断对象是否属于某个派生类:

#include <iostream>
#include <cstdlib>
#include <ctime>

class Base { 
public: 
    virtual ~Base() {}  // 需要虚函数,确保 dynamic_cast 可以工作
};

class Derived : public Base { 
public: 
    void show() { std::cout << "Derived\n"; } 
};

// 随机返回 Base*,可能指向 Base 或 Derived
Base* getSomeBasePointer() {
    if (std::rand() % 2) {
        return new Derived();  // 可能返回 Derived*
    } else {
        return new Base();  // 可能返回 Base*
    }
}

int main() {
    std::srand(std::time(nullptr));  // 初始化随机数种子

    Base* ptr = getSomeBasePointer();
    Derived* derivedPtr = dynamic_cast<Derived*>(ptr);

    if (derivedPtr) {
        std::cout << "This is a Derived object.\n";
        derivedPtr->show();
    } else {
        std::cout << "Not a Derived object.\n";
    }

    delete ptr;  // 释放内存,防止泄漏
    return 0;
}

3. 用于引用类型的转换

当转换失败时,dynamic_cast 在指针转换返回 nullptr,但在 引用类型转换 时抛出 std::bad_cast 异常:

try {
    Base& baseRef = someFunctionReturningBaseRef();
    Derived& derivedRef = dynamic_cast<Derived&>(baseRef);  // 失败会抛异常
    derivedRef.show();
} catch (const std::bad_cast& e) {
    std::cout << "Bad cast: " << e.what() << '\n';
}

注意到这句话const auto *value_node = dynamic_cast<const TrieNodeWithValue<T> *>(curr.get());

继承关系:

  • curr是一个指向基类TrieNode的指针/智能指针
  • TrieNodeWithValue是TrieNode的派生类
  • 在运行时,curr可能指向基类对象或派生类对象

为什么需要dynamic_cast:

  • C++是静态类型语言,编译时只知道curr的静态类型是TrieNode
  • 要在运行时确认对象的实际类型(是否为TrieNodeWithValue),需要使用dynamic_cast
  • dynamic_cast会进行运行时类型检查,如果类型转换失败会返回nullptr,这是C++中进行运行时类型识别(RTTI)的标准方式。需要注意的是,要使用dynamic_cast,基类必须是多态的(含有至少一个虚函数)。

2.2 PUT方法

主要分为递归和非递归两种写法,这里提供一种递归的参考思路:

template <class T>
auto Trie::Put(std::string_view key, T value) const -> Trie {
    // 使用递归lambda构造新的Trie树,实现持久化更新(copy-on-write)
    auto put_helper = [&](auto &self, const std::shared_ptr<const TrieNode> &node, size_t depth)
                           -> std::shared_ptr<const TrieNode> {
        if (depth == key.size()) {
            std::map<char, std::shared_ptr<const TrieNode>> children;
            if (node != nullptr) {
                children = node->children_;
            }
            return std::make_shared<TrieNodeWithValue<T>>(children, std::make_shared<T>(std::move(value)));
        }

        char c = key[depth];
        std::shared_ptr<const TrieNode> child = nullptr;
        if (node != nullptr && node->children_.find(c) != node->children_.end()) {
            child = node->children_.at(c);
        }

        auto new_child = self(self, child, depth + 1);

        if (node != nullptr) {
            auto new_node = std::shared_ptr<const TrieNode>(node->Clone());
            auto mut_children = const_cast<std::map<char, std::shared_ptr<const TrieNode>> *>(&new_node->children_);
            (*mut_children)[c] = new_child;
            return new_node;
        }
        std::map<char, std::shared_ptr<const TrieNode>> new_children;
        new_children[c] = new_child;
        return std::make_shared<TrieNode>(new_children);
    };

    auto new_root = put_helper(put_helper, root_, 0);
    return Trie(new_root);
}

主要的实现细节在于判断当前节点是否存在,如果存在,则生成一个节点的复制(这是因为我们要返回一个新的trie实例),否则创建一个新的根节点,这里我们要妥善处理好节点的复制。

auto mut_children = const_cast<std::map<char, std::shared_ptr<const TrieNode>> *>(&new_node->children_);
(*mut_children)[c] = new_child;

通过 const_castconst 限制去除,以便可以对 children_ 进行修改

2.3 REMOVE方法

auto Trie::Remove(std::string_view key) const -> Trie {
    if (root_ == nullptr) {
        return {};
    }

    // 使用DFS递归删除节点
    std::function<std::shared_ptr<const TrieNode>(std::shared_ptr<const TrieNode>, std::string_view, size_t)>
        remove_helper;
    remove_helper = [&remove_helper](std::shared_ptr<const TrieNode> node, std::string_view key,
                                     size_t depth) -> std::shared_ptr<const TrieNode> {
        if (node == nullptr) {
            return nullptr;
        }

        if (depth == key.length()) {
            // 到达key的末尾,检查是否是值节点
            if (!node->is_value_node_) {
                return node;
            }
            // 如果没有子节点,返回nullptr;否则返回一个普通的TrieNode
            if (node->children_.empty()) {
                return nullptr;
            }
            return std::make_shared<TrieNode>(node->children_);
        }

        char c = key[depth];
        if (node->children_.find(c) == node->children_.end()) {
            return node;
        }
        
        auto new_node = std::shared_ptr<const TrieNode>(node->Clone());
        auto mut_children = const_cast<std::map<char, std::shared_ptr<const TrieNode>> *>(&new_node->children_);

        auto child = remove_helper(node->children_.at(c), key, depth + 1);
        if (child == nullptr) {
            mut_children->erase(c);
            if (mut_children->empty() && !new_node->is_value_node_) {
                return nullptr;
            }
        } else {
            (*mut_children)[c] = child;
        }

        return new_node;
    };

    auto new_root = remove_helper(root_, key, 0);
    return Trie(new_root);
}

最后,我们末尾还需要加上几句话,这是因为,在cpp文件末尾,需要显式实例化你要支持的类型,for example:

template auto Trie::Get<int>(std::string_view) const -> const int *;
template auto Trie::Get<std::string>(std::string_view) const -> const std::string *;
template auto Trie::Put<int>(std::string_view, int) const -> Trie;
template auto Trie::Put<std::string>(std::string_view, std::string) const -> Trie;

到这里就算完成task1的基本内容了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值