Welcome to My Blog

欢迎光临!文章不定期更新,希望能对您有帮助 :)

C++ 踩坑记录(持续更新!)

有人说你永远不能自称精通 C++,本文试图为这个观点提供一个例证。下面列出了一些从去年(2024)开始我编写 C++ 代码时犯过的错误。当然,有些可能看上去很蠢,不过谁又能在未知全貌的时候保证自己不会犯错呢?我认为这些错误至少初看上去是反直觉的。 std 集合操作只能用于有序容器 你要表示两个整数集合,所以你用了 std::unordered_set<int>。之后你想要求两个集合的交集,你搜了一下 STL,发现 std::set_intersection 似乎正合适。于是你写了一个简单的程序 // test_set.cpp std::unordered_set<int> set1 = {1, 2, 3, 4, 5}; std::unordered_set<int> set2 = {4, 5, 6, 7, 8}; std::unordered_set<int> result; std::set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(), std::inserter(result, result.begin())); for (const auto &elem : result) { std::cout << elem << " "; } std::cout << std::endl; 然后运行,你期望输出的结果是 4 5 或是 5 4(毕竟你很严谨)。可是实际结果呢 $ clang++ test_set.cpp -o test_set $ ./test_set 什么都没有输出。 正如 std::set 实际上表示的是有序集合一样,std::set_intersection 实际上也是 ordered_set_intersection,只不过函数签名并不告诉你。...

九月 11, 2025 · 3 分钟 · 567 字 · Wokron

我该用什么参数类型?

本文我们讨论一个很小的问题:不同的函数参数类型都在什么情况下使用? 参数分类 比如我们有一个类型 T(让问题先简单点,此处 T 并非范型),那么要在函数中传入一个该类型的参数,共有几种可能?我们可以分一下类: 修饰符:有 const 修饰、无 const 修饰 是否引用:值、左值引用、右值引用 按照这个分类,我们总共能得出 5 种参数类型 const修饰 是否引用 参数类型 有 值 const T 有 左值引用 const T& 无 值 T 无 左值引用 T& 无 右值引用 T&& const T&& 是没有意义的 接下来介绍这些参数类型的应用场景 转移所有权的情况 假设需要将参数的生命周期转移到函数内,那么大多数情况下,直接使用 T 作为参数类型即可。 例如我们有类型 A,其构造时会接受并持有一个 std::vector<int> 参数。那么参数类型应当为 std::vector<int>。 class A { public: A(std::vector<int> v) : v_(std::move(v)) {} private: std::vector_<int> v_; }; 我们考虑两种构造类型 A 对象的情况。其一是我们在传入 std::vector<int> 对象后不会再使用该对象。这时我们可以采取移动语义。 std::vector<int> v; // ... A a(std::move(v)); 其二是我们在传入 std::vector<int> 对象后还会使用该对象。这是我们需要采取复制语义。...

八月 1, 2025 · 2 分钟 · 364 字 · Wokron

试试触发(几乎)所有信号

输入 kill -L,你可以看到 Linux 下所有可用的标准信号,总共有 31 个。 $ kill -L 1 HUP 2 INT 3 QUIT 4 ILL 5 TRAP 6 ABRT 7 BUS 8 FPE 9 KILL 10 USR1 11 SEGV 12 USR2 13 PIPE 14 ALRM 15 TERM 16 STKFLT 17 CHLD 18 CONT 19 STOP 20 TSTP 21 TTIN 22 TTOU 23 URG 24 XCPU 25 XFSZ 26 VTALRM 27 PROF 28 WINCH 29 POLL 30 PWR 31 SYS 这回试试在这些信号原本的应用场景下触发它们。...

五月 23, 2025 · 10 分钟 · 2058 字 · Wokron

Linux 进程的内存管理

虽然我们都学过一个进程的内存由堆和栈组成。但是这样的模型还是太抽象了,其中掩盖了许多操作系统的细节。所以这里简单梳理一下进程的内存管理有关知识。 堆的增长 libc 中提供 malloc 函数申请堆上内存。底层由 brk 系统调用负责申请堆上内存。 在内核的视角下,堆空间是一个简单的结构。它由一个固定的堆底(符号 end)和可变的堆顶(称为 program break)组成。内核所要做的就是根据用户设定的 program break 将堆底和堆顶之间的内存标为有效。而 brk 系统调用的作用便是将某一地址设置为堆顶。 在 brk 系统调用之上,glibc 提供了两个不同的函数 int brk(void *addr) 和 void *sbrk(intptr_t increment)。前者直接设置 program break 地址,后者则根据 increment 取值调整 program break 位置。 下面使用 sbrk() 函数进行内存分配。sbrk() 会返回调用之前原本的 program break 地址,因此还需额外进行加减以获取当前地址。这一语义比较合理,因为这样 brk(N) 和 malloc(N) 的返回值都代表新分配的内存的起始地址。 #include <stdio.h> #include <unistd.h> extern char end; int main() { printf("Address of end symbol: %p\n", (void *)&end); printf("Current program break: %p\n", sbrk(0)); printf("New program break: %p\n", sbrk(1024) + 1024); printf("After deallocation: %p\n", sbrk(-512) - 512); } 运行结果如下...

五月 17, 2025 · 7 分钟 · 1484 字 · Wokron

写个编译期排序

这回摆弄一下模板。 C++ 里有一个 std::integer_sequence。可以定义编译期整数序列。比如说 #include <utility> using my_seq = std::integer_sequence<int, 1, 2, 3, 4, 5>; 接下来写一个 trait seq_sort_t,实现编译期排序。类似于: using my_seq = std::integer_sequence<int, 2, 5, 3, 1, 4>; using sorted_my_seq = seq_sort_t<my_seq>; // std::integer_sequence<int, 1, 2, 3, 4, 5> 首先做一些准备工作。 运行时输出 通过类型萃取,将 std::integer_sequence 转换为运行时的 std::initializer_list。方便输出结果。 template <typename S> struct seq_to_init_list; template <typename T, T... Is> struct seq_to_init_list<std::integer_sequence<T, Is...>> { static constexpr std::initializer_list<T> value = {Is...}; }; template <typename S> constexpr auto seq_to_init_list_v = seq_to_init_list<S>::value; 用法如下:...

三月 7, 2025 · 6 分钟 · 1114 字 · Wokron

Corio - 基于 Asio 的 C++20 协程框架

最近研究了一下协程和网络编程,结合 C++20 的 Coroutine 和 Asio 编写了一个轻量级的协程框架 Corio。Corio 与 Asio 无缝集成,提供了多线程运行时支持和灵活的协程控制接口。 项目仓库:wokron/corio。 安装 corio 是一个 Header-only Library。因此只需要将本仓库 include/ 路径下的一系列头文件放在你的项目中指定位置,并将该位置添加到编译器的包含路径中即可。 以 CMake 为例,为了保持项目的模块化,可以选择将本项目作为 git 子模块。 git submodule add https://github.com/wokron/corio.git ./your/path/to/corio 随后在 CMakeLists.txt 中引入 corio。 add_library(corio INTERFACE) target_include_directories(corio INTERFACE ./your/path/to/corio/include) target_link_libraries(corio INTERFACE ${ASIO_LIBRARY}) 如前所述,Corio 依赖了 Asio。因此为了使用 Corio,还需安装 Asio(非 Boost 版本)。 最后,在你的代码中包含 corio.hpp 头文件以引入 corio。corio 的所有功能均位于 corio:: 命名空间下。 #include <corio.hpp> 使用 协程类型 lazy corio::Lazy<T> 是 corio 库的核心。通过将函数的返回值设定为 Lazy<T>,我们将该函数定义为协程。在 Lazy<T> 所定义的协程中,不再使用 return,而是使用 co_return 以便从协程中返回。其中 T 为该协程的返回值类型。...

一月 21, 2025 · 6 分钟 · 1215 字 · Wokron