NebulaChat项目构建笔记

一、nlohmann::json 常见用法

1. 引入和别名

复制代码
#include <nlohmann/json.hpp>
using json = nlohmann::json;

以后就可以直接写 json 这个类型。

2. 字符串 -> JSON 对象(parse)

在服务器里最常用的就是这句:

cpp 复制代码
json req = json::parse(line);

含义:

把一段 JSON 格式的字符串 line 解析成一个 json 类型的对象。

典型场景:客户端发来的请求:

cpp 复制代码
{"cmd":"echo","msg":"hi"}

服务器:

cpp 复制代码
try {
    json req = json::parse(line);
} catch (const std::exception& e) {
    // 说明 line 不是合法 JSON,直接按"坏请求"处理
}

3. 访问字段:operator[]value()

假设有:

cpp 复制代码
json req = json::parse(R"({"cmd":"echo","msg":"hi"})");

常用几种访问方式:

3.1 下标访问:operator[]
cpp 复制代码
std::string cmd = req["cmd"];  // 如果没有 "cmd" 会抛异常

特点:

  • key 不存在、类型不匹配,可能抛异常

  • 适合你"确定一定有"的字段

3.2 安全访问:value(key, default)
cpp 复制代码
std::string cmd = req.value("cmd", "");
std::string msg = req.value("msg", "");
int age        = req.value("age", 0);      // 不存在就用 0
bool close     = req.value("close", false);

规则:

  • "cmd" 存在 → 转成你给的默认值类型(这里是 std::string

  • "cmd" 不存在 → 直接返回第二个参数(默认值)

4. 构造 JSON 响应

现在在 processLine 里大概是这样写的:

cpp 复制代码
json resp;
resp["ok"]   = true;
resp["data"] = msg;

也可以一口气用初始化列表:

cpp 复制代码
json resp = {
    {"ok", true},
    {"data", msg}
};

数组也很简单:

cpp 复制代码
json arr = json::array();
arr.push_back(1);
arr.push_back(2);
arr.push_back(3);
// 或者
json arr2 = {1, 2, 3};

5. JSON -> 字符串:dump()

cpp 复制代码
std::string out = resp.dump();       // 一行压缩形式
std::string pretty = resp.dump(4);   // 带缩进的格式化输出(4 空格缩进)

在网络里用的一般是第一种:

复制代码
return resp.dump() + "\n";

这样客户端拿到的是一行 JSON,方便你用按行协议拆包。

6. 错误处理(parse 出错)

现在的模板:

cpp 复制代码
std::string Server::processLine(const std::string& line){
    json resp;
    try {
        json req = json::parse(line);
        // 正常业务逻辑......
    } catch (const std::exception& e) {
        resp["ok"]  = false;
        resp["err"] = "bad json";  // 或 resp["err"] = e.what();
    }
    return resp.dump() + "\n";
}

只要 parse 失败(不是合法 JSON),就会进 catch,就可以统一返回一个错误 JSON。

7. 小结一句话

最常用:

  1. json req = json::parse(line); 解析请求

  2. req.value("key", default) 读字段

  3. resp["key"] = value; resp.dump() 构造响应

二、std::map::find 返回值说明

以项目常见的这种为例:

cpp 复制代码
std::map<int, std::unique_ptr<Connection>> conns_;

1. find 返回什么?

复制代码
auto it = conns_.find(fd);

find 返回的是一个"迭代器"(iterator),可以理解成:

  • 指向 map 里某一个元素的"指针样的东西"

map 的元素类型是:

复制代码
std::pair<const Key, T>

在例子里:

cpp 复制代码
Key = int
T   = std::unique_ptr<Connection>

*it 的类型是:std::pair<const int, std::unique_ptr<Connection>>

2. 怎么判断有没有找到?

cpp 复制代码
auto it = conns_.find(fd);
if (it == conns_.end()) {
    // 没找到
} else {
    // 找到了
}

end() 表示"序列末尾的下一个位置",也就是"找不到"的标准写法。

这是非常固定的模式:

cpp 复制代码
auto it = m.find(key);
if (it != m.end()) {
    // 用 it
}

3. 找到后怎么用?

访问 keyvalue

cpp 复制代码
int key_fd = it->first;                        // key
std::unique_ptr<Connection>& ptr = it->second; // value

Connection& conn = *ptr;                      // 引用
Connection* pconn = ptr.get();                // 原始指针

在项目中经常看到类似:

cpp 复制代码
auto it = conns_.find(fd);
if (it == conns_.end()) return;
Connection& conn = *it->second;

理解为:

  1. it 是"指向某个键值对"的迭代器

  2. it->second 是那个 unique_ptr<Connection>

  3. *it->second 才是 Connection&

4. 结合你的场景:保存一个指针出来

写过:

cpp 复制代码
std::unique_ptr<Connection>* p_holder = nullptr;
{
    std::lock_guard<std::mutex> lock(conns_mtx_);
    auto it = conns_.find(fd);
    if(it == conns_.end()) return;
    p_holder = &it->second;
}
Connection& conn = *p_holder->get();

这里 step by step:

  • conns_.find(fd) → 找到对应的迭代器 it

  • it->secondunique_ptr<Connection>

  • &it->second 就是"指向这个 unique_ptr 的指针"

  • p_holder->get() → 拿到 Connection*

  • *p_holder->get() → 变成 Connection&

这么写的目的是:

在解锁后还能继续访问 Connection 对象本身(但已经不动 map 的结构了)。

三、智能指针总结(unique_ptr / shared_ptr / weak_ptr)

1. 为什么要用智能指针

裸指针(T*)的问题:

  • 忘记 delete → 内存泄漏

  • new / delete 分散在各处,不好维护

  • 异常、提前 return 时很容易少写 delete

  • 多个地方持有同一指针时,很容易 double free

智能指针的核心思想:RAII(资源在对象生命周期结束时自动释放)。

2. std::unique_ptr:独占所有权

特点:

  • 一个对象只能有一个 unique_ptr 拥有它

  • 不能拷贝,只能移动

  • 适合"资源有唯一主人"的场景,比如一个连接 Connection 只由 Server

常用写法:

cpp 复制代码
#include <memory>

std::unique_ptr<Connection> conn = std::make_unique<Connection>();

// 访问
conn->fd = 10;      // 像指针
(*conn).fd = 10;    // 像引用

// 传递所有权(移动)
std::unique_ptr<Connection> other = std::move(conn);
// 此时 conn == nullptr,所有权给了 other

// 释放资源
conn.reset();  // 显式释放
// 或者 conn 在作用域结束自动释放

容器里用:

cpp 复制代码
std::map<int, std::unique_ptr<Connection>> conns;
conns.emplace(fd, std::make_unique<Connection>());

3. std::shared_ptr:共享所有权

特点:

  • 可以有多个 shared_ptr 指向同一个对象

  • 内部有引用计数,最后一个 shared_ptr 析构时才真正 delete 对象

  • use_count() 可以查看当前引用数

典型用法:

cpp 复制代码
#include <memory>

auto p1 = std::make_shared<User>("Alice");
{
    auto p2 = p1;  // 引用计数 +1
}                   // p2 析构,计数 -1
// 最后一个 shared_ptr 消失时才 delete User

缺点:

  • 有一点额外开销(引用计数管理)

  • 两个对象互相持有 shared_ptr 时会产生"循环引用",导致内存泄漏(这时需要 weak_ptr)

一般用在:

  • 同一个对象需要被多个模块共享管理

  • 生命周期比较复杂,很难用一个"唯一主人"描述清楚的时候

4. std::weak_ptr:配合 shared_ptr 打破循环引用

特点:

  • 不增加引用计数

  • 只能观察 shared_ptr 管理的对象

  • 需要用 lock() 转成 shared_ptr 才能使用

例子:

cpp 复制代码
std::shared_ptr<Node> a = std::make_shared<Node>();
std::shared_ptr<Node> b = std::make_shared<Node>();

a->next = b;                 // shared_ptr
b->prev = a;                 // 如果也是 shared_ptr,会循环引用

// 解决办法:prev 用 weak_ptr
struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // 不增加引用计数
};

5. 结合你现在的 NebulaChat

你现在的关键结构:

cpp 复制代码
std::map<int, std::unique_ptr<Connection>> conns_;

理解为:

  • conns_ 是"连接管理器"

  • 每个 Connection 只属于 Server 一个地方

  • map 中这个元素被 erase 或者整个 conns_ 析构时,对应连接自动 delete,不用你手动管理内存

结合 map.find:

cpp 复制代码
auto it = conns_.find(fd);
if (it == conns_.end()) return;

Connection& conn = *it->second;  // 从 unique_ptr 拿出真正的对象

这就是 map + unique_ptr + 智能指针三者一起工作的典型模式。

相关推荐
Mr_Hu4041 小时前
鸿蒙开发学习笔记-生命周期小记
笔记·学习·harmonyos·鸿蒙
ULTRA??1 小时前
C++类型和容器在MoonBit中的对应关系整理
开发语言·c++·rust
李白同学1 小时前
C++:queue、priority_queue的使用和模拟实现
开发语言·c++
楼田莉子1 小时前
Linux学习:基础IO相关学习
linux·开发语言·c++·后端·学习
小陈phd1 小时前
langgraph从入门到精通(一)——langgraph概念解析
linux·运维·数据库
.小小陈.1 小时前
C++初阶5:string类使用攻略
开发语言·c++·学习·算法
inquisiter1 小时前
cove-salus-tellus测试程序时序逻辑
linux·服务器·网络·riscv
这儿有一堆花1 小时前
告别“脚本小子”:真正理解 Linux 包管理器
linux
神奇的代码在哪里1 小时前
C++的演进与我的编程学习之旅:从底层基础到AI应用
c++·人工智能·python·学习·程序人生·个人开发
摇滚侠1 小时前
2025最新 SpringCloud 教程,Gateway-过滤器-基本使用,笔记58
笔记·spring cloud·gateway