C++11 std::mutex
多线程程序里最容易出问题的地方之一,就是多个线程同时读写共享数据。std::mutex 就是 C++11 提供的最基础互斥锁,用来保护临界区。
1. 为什么需要互斥锁?来看一个简单场景:两个线程同时给同一个计数器加一。如果没有同步保护,结果就可能出现竞态条件,最终值未必正确。
2. 基本用法12345678910111213141516171819202122232425262728#include <thread>#include <mutex>#include <iostream>using namespace std;std::mutex mtx;int counter = 0;void work(){ for (int i = 0; i < 1000; ++i) { mtx.lock(); ++counter; mtx.unlock(); }}int main(){ thread t1(wor ...
C++11 std::thread
C++11 以前,C++ 并没有统一的标准线程库,多线程代码往往依赖平台 API。从 C++11 开始,标准库正式提供了 std::thread,多线程编程终于有了统一入口。
1. 基本创建方式123456789101112131415#include <thread>#include <iostream>using namespace std;void work(){ cout << "thread running" << endl;}int main(){ std::thread t(work); t.join(); return 0;}
这里 t 就代表一个新线程。
2. join 和 detach2.1 join1t.join();
表示等待线程执行结束。这通常是最常见、也最稳妥的处理方式。
2.2 detach1t.detach();
表示让线程独立运行,当前线程不再等待它。这种方式用起来要更小心,因为 ...
完美转发(std::forward)
完美转发的目标可以用一句话概括:把收到的参数,尽可能原样地转交给下一个函数。它是模板代码里非常常见的一项能力,核心工具就是 std::forward。
1. 为什么需要它?假设我们写一个中转函数:
123456789void target(const std::string& s){}template <typename T>void wrapper(T&& arg){ target(arg);}
看起来没问题,但这里的 arg 在函数体里其实是一个左值。也就是说,原本传进来如果是右值,中间这层包装后,右值语义可能就丢了。
2. std::forward 的作用正确写法通常是:
12345template <typename T>void wrapper(T&& arg){ target(std::forward<T>(arg));}
这样 arg 的值类别就能尽量保持不变。
3. 一个更直观的例 ...
右值引用与移动语义
右值引用 和 移动语义 是 C++11 里非常核心的一组能力。它们解决的问题很实际:避免对象在传递和返回时发生不必要的深拷贝。
1. 什么是右值引用?右值引用的语法是 &&:
1int&& x = 10;
这里的 10 是一个右值,x 用右值引用绑定了它。
2. 它为什么重要?在老 C++ 里,大对象传来传去通常依赖拷贝。比如一个 std::vector 或大字符串,拷贝成本可能很高。
C++11 通过右值引用区分出“这个对象马上就不用了”,于是资源就可以直接“搬走”,而不是重新复制一份。
3. 移动和拷贝的区别拷贝
复制资源
原对象和新对象各有一份独立数据
移动
转移资源所有权
原对象通常变成“空但有效”的状态
4. 一个简单示例12345678910111213#include <iostream>#include <vector>using namespace std;int main(){ vector<int> a{1, 2, 3, 4}; ...
override和final
override 和 final 是 C++11 给面向对象代码补上的两个很实用的标记。它们的价值不在运行时功能,而在于让编译器帮你提前发现继承体系里的错误。
1. override 是干什么的?override 用来明确表示:这个虚函数是用来重写基类函数的。
123456789101112131415class Base{public: virtual void show() const { }};class Derived : public Base{public: void show() const override { }};
如果函数签名写错了,编译器会直接报错。
2. 为什么 override 常用?没有 override 时,很容易写出“以为重写了,实际上没重写”的代码:
1234567891011class Base{public: virtual void show() const {} ...
静态断言(static_assert)
static_assert 是 C++11 提供的编译期断言工具。它和运行时的 assert 最大区别在于:如果条件不满足,程序根本编不过。
1. 基本语法1static_assert(condition, "message");
如果 condition 为假,编译器会直接报错,并带上你写的提示信息。
2. 一个最简单的例子1static_assert(sizeof(int) == 4, "int size must be 4");
这类写法经常用于平台相关检查。
3. 和 assert 的区别assert
运行时检查
程序先能编译通过
static_assert
编译期检查
条件不满足时直接停止编译
所以 static_assert 更适合检查那些在编译阶段就应该成立的前提。
4. 常见使用场景4.1 类型大小检查1static_assert(sizeof(long long) >= 8, "need 64-bit integer");
4.2 模板参数限制1234 ...
强类型枚举(enum class)
C++11 的 enum class 主要是为了解决传统枚举的两个老问题:作用域污染和类型不够严格。
1. 传统枚举的问题1234567891011enum Color { Red, Green, Blue};enum TrafficLight { Red, Yellow, Green};
上面这段代码就有明显问题:不同枚举里的名字会跑到同一个作用域里,容易冲突。
另外,传统枚举和整数之间的转换也比较宽松,不够严谨。
2. C++11 的 enum class12345enum class Color { Red, Green, Blue};
这时访问枚举值时必须带上作用域:
1Color c = Color::Red;
3. 它的两个主要优点3.1 不污染外部作用域12enum class Color { Red, Green };enum class TrafficLight { Red, Yel ...
constexpr常量表达式
constexpr 是 C++11 引入的一个很重要的关键字。它最核心的意义可以概括成一句话:让某些值或函数有机会在编译期完成计算。
1. 为什么它常用?在早期 C++ 里,常量通常这样写:
1const int size = 10;
但 const 不等于“编译期常量表达式”。而 constexpr 更强调的是:这个值在语义上就是一个编译期可求值的常量。
2. constexpr变量1constexpr int size = 10;
这种变量通常可以直接用于需要编译期常量的地方,例如数组大小、模板参数等。
3. constexpr函数3.1 基本示例1234constexpr int square(int x){ return x * x;}
使用:
1constexpr int ret = square(5);
这里 ret 可以在编译阶段直接得到结果。
4. 一个完整例子1234567891011121314#include <iostream>using namespace std;constexpr ...
C++
未读类型别名与using
C++11 的 using 在很多人眼里只是 typedef 的新写法,但它真正的价值在于:写法更直观,尤其是在模板别名场景下比 typedef 好用很多。
1. typedef 的痛点传统类型别名通常这样写:
12typedef unsigned long ulong;typedef int (*Func)(int, int);
简单类型还好,一旦遇到函数指针、模板类型,阅读体验会比较差。
2. using 的基本写法12using ulong = unsigned long;using Func = int (*)(int, int);
这种形式更像“把右边这个类型命名为左边这个名字”,可读性通常会更好。
3. 函数指针例子12345678910111213141516#include <iostream>using namespace std;using Func = int (*)(int, int);int add(int a, int b){ return a + b;}int main(){ ...
C++
未读nullptr空指针
C++11 引入的 nullptr,本质上是为了替代老写法里的 NULL 和 0。它解决的核心问题不是“能不能表示空指针”,而是“能不能明确、无歧义地表示空指针”。
1. 老问题出在哪里?在旧代码里,空指针经常这样写:
12int* p = NULL;int* q = 0;
这在很多情况下能工作,但 NULL 本质上往往只是一个整数常量宏。一旦遇到函数重载,问题就会暴露出来。
123456789void func(int){}void func(char*){}func(NULL); // 可能调用到 int 版本
这显然不是我们想要的结果。
2. C++11 的 nullptrnullptr 是一个专门用于表示空指针的关键字:
1int* p = nullptr;
它的类型是 std::nullptr_t,不是普通整数,因此在重载解析里更安全。
3. 最典型的好处:函数重载更准确123456789101112131415161718#include <iostream>using namespace s ...










