现代c++

无名猿3 天前
c++·性能优化·并发编程·现代c++
手写一个线程池:任务队列、工作线程与停止顺序的三个设计点每个任务 new 一个 std::thread 是并发编程里最贵的偷懒方式:线程创建要走系统调用,栈空间按兆算,销毁又要再来一遍。当任务本身只有几微秒时,你花在「造线程」上的时间会比干活多几十倍。 线程池的思路很朴素:线程只创建一次,任务排队,谁空谁取。这篇从零把它写出来,然后把三个最容易写错的设计点讲透。
无名猿3 天前
c++·性能优化·并发编程·现代c++
锁进阶:atomic_flag 自旋锁、shared_mutex 读写锁与无锁的代价std::mutex 不是免费的:一旦发生竞争,抢不到锁的线程会被挂进内核,等锁释放再被唤醒,这一挂一醒就是两次上下文切换,微秒级开销。可如果你的临界区只有「++counter」这么短,等锁的时间可能比干活的时间还长。这篇讲清锁的三条进阶路线:极短临界区用自旋锁(atomic_flag 实现)、读多写少用读写锁(shared_mutex)、以及「别迷信无锁,先测再选」。
无名猿5 天前
c++·性能优化·内存管理·现代c++·语法基础
移动构造与移动赋值:把资源偷过来「std::move 是什么」那一篇已经讲清楚了:它只是个类型转换,真正搬数据的是移动构造函数。这一篇换个视角,从类的实现者出发,亲手写出正确、高效的移动操作。有几个问题只有写实现时才碰得到:noexcept 为什么不是装饰、移动后源对象到底处于什么状态、移动赋值怎么处理自赋值。还有一个更隐蔽的:编译器可能压根没给你生成移动操作。
无名猿5 天前
c++·现代c++·语法基础
成员初始化列表与初始化顺序陷阱:按声明顺序,不是书写顺序a_(b_), b_(x) 这种初始化列表,在评审里特别容易被放过去:两行都写对了成员名、都写对了实参,编译器也不一定报错。但如果 b_ 的声明在 a_ 后面,那 a_(b_) 读的就是一个还没初始化的 b_。这不是「值不对」,是未定义行为(undefined behavior)。这篇把成员初始化列表的规则、顺序陷阱和性能差异一次讲清,结尾给一条可以直接照抄的纪律。
无名猿6 天前
c++·内存管理·并发编程·现代c++
shared_ptr 的线程安全边界:计数安全不等于对象安全std::shared_ptr 大概是 C++ 里被误解最多的类型。很多人听说「它的引用计数是线程安全的」,就放心地把它当全局变量、在线程之间随手赋值,然后在大压力下偶发崩溃或者计数错乱。
无名猿6 天前
c++·性能优化·并发编程·现代c++
shared_mutex 读写锁该不该用,以及 volatile 的三大误解「读多写少,那用读写锁肯定更快吧。」这句话是并发优化里最常见的直觉,也是最常翻车的一个。std::shared_mutex(C++17)确实做到了多读者并存、写者独占,但它的独占成本比 std::mutex 高,读者路径还要动一个共享计数器。下面先讲清该怎么用、什么时候别用,再顺手把 volatile 的三大误解拆掉。这两件事总是一起出现,因为动机都一样:想省掉那把锁。
无名猿6 天前
c++·性能优化·内存管理·标准库·现代c++
unique_ptr 完全指南:独占所有权与零开销裸指针最大的麻烦其实不是「会忘写 delete」,而是所有权含糊不清:这段内存归谁,谁来释放,读代码的人只能猜。std::unique_ptr 把答案钉死在类型里,同一时刻只有一个 unique_ptr 拥有这块内存,要转移就必须显式写 std::move。代价几乎为零,既不比裸指针多占内存,也不比它多花时间。下面把常用接口和几个容易踩的点一起讲清。
无名猿6 天前
c++·内存管理·调试技巧·现代c++
对象构造与析构顺序全解:声明顺序、继承链与逆序析构「对象是怎么被拼出来、又怎么被拆掉的」是 C++ 里最容易想错的一件事,偏偏它贯穿所有资源管理。一个常见的坑:你在初始化列表里把成员 b 写在 a 前面,就以为 b 先构造。其实不是,先后只认声明顺序。再比如通过基类指针 delete 一个派生对象,若基类析构不是 virtual,派生部分的析构函数压根不会被调用。这篇把这几条顺序规则一条条用实跑代码钉死,配上时序图,半年后翻回来照着图就能复现。
无名猿6 天前
c++·内存管理·现代c++·踩坑记录
拷贝构造与拷贝赋值:调用时机、深浅拷贝与复制消除「为什么我这行 T b = a; 调用的是拷贝构造,而 b = a; 却是拷贝赋值?」「为什么函数按值返回明明该拷贝两次,实测却只构造了一次?」拷贝语义是 C++ 里最容易「想当然」、却处处是坑的一块。这篇不堆定义,直接上打印计数器:把拷贝构造的触发时机、深浅拷贝的后患、以及编译器悄悄做的「复制消除(copy elision)」优化,用实跑输出一条条亮出来。
无名猿6 天前
c++·内存管理·现代c++·语法基础
裸指针还能不能用:不拥有与拥有的边界「裸指针还能不能用?」网上一边倒的答案是「别用,全换成 std::unique_ptr」。这话对,但只对了一半。C++ Core Guidelines 的真正立场更精细:裸指针(a T*)本身没有错,错的是用它去表达「所有权」。指针可以放心地当「观察者」用,只要你约定它不负责释放。这篇把「不拥有」和「拥有」的边界划清楚,并给你能直接套用的签名写法。
无名猿6 天前
c++·内存管理·现代c++·语法基础
左值、右值、纯右值、将亡值:值类别入门你有没有写过 std::move(x) 之后发现它还能被读取,或者重载了 f(T&) 和 f(T&&) 却搞不清到底调了哪个?这些怪现象的根都在「值类别(value category)」上。C++11 起给每个表达式贴了一张隐形标签,它直接决定一个表达式能不能取地址、能不能被移动、以及重载决议时该选哪个版本。这篇把五个概念一次讲清,并用真跑出来的输出告诉你每种实参选中了哪个函数。
无名猿7 天前
c++·现代c++·语法基础
构造函数详解:默认、委托、转换与 explicit 到底写在哪std::vector<int> v{10} 和 std::vector<int> v(10) 编译都不报错,可前者得到「一个元素,值是 10」,后者得到「10 个元素,全是 0」。同一个类、同样的参数,只换了括号,语义就完全变了。
无名猿7 天前
c++·stl·标准库·现代c++
std::variant 完全指南:类型安全的 union 与 std::visit 用法裸 union 是 C 语言留下来的老物件:它能把不同类型的值叠在同一块内存上,但没人记得住当前存的是哪个类型,读错成员就是未定义行为(undefined behavior, UB),编译器一声不吭。C++17 的 std::variant<A, B, C> 把「联合体」和「类型标签」绑成了一个类型,从此读错类型会得到明确的异常或 nullptr,而不是一份随机数据。
无名猿8 天前
c++·性能优化·内存管理·标准库·现代c++
shared_ptr 完全指南:引用计数、控制块与开销std::unique_ptr 把「独占所有权」钉死在类型上,但现实里经常需要多个对象同时引用同一份资源:缓存、父子节点、观察者列表……这时 std::shared_ptr(共享所有权指针,shared pointer)登场。它用引用计数(reference count)记录「现在有几个人在用」,最后一个离开时才释放。这篇把控制块里到底存了什么、它比裸指针贵在哪、以及什么时候不该用它一次讲清。
无名猿10 天前
c++·现代c++·语法基础·踩坑记录
C++ lambda 捕获列表详解:值捕获、引用捕获、初始化捕获与悬垂引用捕获列表是整个 lambda 里唯一「没有默认值可选」的部分:你必须明确写出 []、[x]、[&x] 还是 [=]。它决定了三件事 —— 闭包能不能活过被捕获变量的生命周期、闭包对象占多少字节、以及调用时看到的是「定义那一刻的快照」还是「变量的实时值」。这三件事全是踩坑高发区,本文用真跑的输出把它们一个个钉死。
无名猿10 天前
c++·标准库·现代c++·语法基础
std::optional 完全指南:别再用 -1 和 nullptr 表达「没有值」用 -1 表示「没找到」、用 nullptr 表示「没配置」,这类约定在 C++ 里活了几十年,代价是每个调用方都得记住「-1 是特殊值」并且每次都记得判断。C++17 的 std::optional<T> 把这件事变成类型系统的一部分:函数的返回值类型直接写着「可能没有值」,编译器会逼着调用方处理空的情况。它不涉及堆分配、不引入间接跳转,值就躺在对象内部。
无名猿11 天前
c++·现代c++·语法基础·踩坑记录
nullptr 取代 NULL 和 0:空指针的三种写法和唯一正确解0 是整数,NULL 在多数实现里也只是 0 的宏,而 nullptr 是 C++11 引入的、专门表示空指针的独立类型 std::nullptr_t。这一个差别,在函数重载和模板推导里能悄无声息地选错版本。这篇用 static_assert + std::is_same 把三种写法的真实类型钉出来,并说清那个经典陷阱 f(NULL)。
无名猿11 天前
c++·内存管理·现代c++·踩坑记录
new/delete 与 malloc/free:为什么绝对不能混用很多 C++ 新手把 new/delete 当成「C++ 版的 malloc/free」来用,结果在析构不执行、资源泄漏、甚至程序崩溃里打转。本质区别在于:new 不只是申请内存,它还调用构造函数;delete 不只是释放内存,它还调用析构函数。而 malloc/free 只认字节、不认对象。一旦把两边混着用,等于让「管生命周期的」和「只管内存的」两套机制互相越权——后果是未定义行为(undefined behavior)。本文用实跑代码把这条红线画清楚。
无名猿12 天前
c++·现代c++·模板元编程·语法基础
左值引用、右值引用与万能引用:引用折叠全解你大概见过 std::vector::push_back(T&&) 既能接左值又能接右值,也见过 std::forward 能把参数「原样」转发出去。它们背后是同一套机制:引用有三种(左值引用、右值引用、万能引用),而编译器用「引用折叠(reference collapsing)」把多层 &/&& 压成最终的一种。这篇把这套规则拆开,并用真跑的输出证明「具名右值引用其实是左值」这个反直觉结论。
无名猿12 天前
c++·内存管理·现代c++·语法基础
对象存储期全解:automatic / static / dynamic / thread「这个变量什么时候被销毁?」答案不取决于它在哪一行写的,而取决于它的存储期(storage duration)。C++ 里有四种存储期:自动(automatic)、静态(static)、动态(dynamic)、线程(thread_local)。它们常和作用域(scope)被混为一谈——但作用域只决定「名字在哪段代码里看得见」,存储期才决定「对象活到什么时候」。本文用构造/析构的真实输出把四种存储期钉死。