C++ 11 属性
在 C++11 之前,各家编译器都有自己的标记方式:
-
GCC:
__attribute__((xxx)) -
MSVC:
__declspec(xxx) -
Clang 又是另一套
写跨平台代码非常痛苦。
C++11 统一了标准:
[[noreturn]] [[deprecated]] [[nodiscard]] [[fallthrough]]
让程序员用一个统一语法,在所有编译器中都能工作。
① 告诉编译器某函数不会返回:[[noreturn]]
[[noreturn]] void fatal(const char* msg) { throw msg; // 永远不会"正常返回" }
用途:
-
编译器可进行更大胆的优化
-
防止出现"控制流可能未返回"的错误警告
-
更好地分析控制流
② 标记弃用的函数:[[deprecated]]
[[deprecated("Use foo2 instead")]] void foo();
调用 foo() 时编译器会警告。
用于:
-
API 版本升级
-
库作者告知用户不要再使用某接口
③ 要求调用者必须用返回值:[[nodiscard]]
[[nodiscard]] int compute();
如果别人写:
compute(); // 会警告:你丢掉了一个有意义的返回值!
用途:
- 防止错误忽略(如错误码、资源句柄等)
④ 明确 switch 中 fallthrough 是故意的
switch (x) { case 1: doSomething(); [[fallthrough]]; case 2: doSomethingElse(); }
用途:
-
防止编译器误判为忘记写
break -
提高代码意图可读性
⑤ 优化相关属性(因不同标准继续增加)
例如 [[likely]], [[unlikely]](C++20):
if (error [[unlikely]]) { ... }
让编译器更优化分支预测。
实际工程中最常用的属性
| 属性 | 作用 | C++ 引入版本 |
|---|---|---|
[[noreturn]] |
函数不会返回 | C++11 |
[[deprecated]] |
标记旧 API,可生成编译警告 | C++14 |
[[nodiscard]] |
防止忽略重要返回值 | C++17 |
[[fallthrough]] |
明确 switch case 穿透 | C++17 |
[[likely]] / [[unlikely]] |
提示分支概率,提高分支预测效率 | C++20 |
最极简一句话总结:
C++11 的属性是一种标准化的方式,让程序员通过
[[...]]语法向编译器提供额外信息,用于优化、警告控制、代码意图更明确,而不改变程序逻辑。
C++11 用户定义字面量(User-Defined Literals, UDL)
C++11 引入用户定义字面量,让我们可以为自己的类型创建新的字面量后缀 。
就像 C++ 内置的:
-
12.5→ double -
12.5f→ float -
0xFFu→ unsigned int
用户也可以自定义新的:
101_mysuffix "hello"_json 3.0_rad
这些都是运算符函数触发的特殊语法。
🧩 1. 用户定义字面量的核心语法
定义方式
operator "" _suffix(参数...)
-
必须以
_suffix(以下划线开头)作为后缀 -
由编译器在看到
xxx_suffix时自动调用 -
返回值类型由你自己决定
例如:
cpp
struct S { int value; };
S operator "" _mysuffix(unsigned long long v)
{
return S{(int)v};
}
S s = 101_mysuffix; // 自动调用上面的 UDL
POD(Plain Old Data)
POD 类型 = 平凡类型(trivial) + 标准布局类型(standard layout)
它强调两点:
-
Plain:行为像普通数据,没有复杂构造/析构,不会有隐藏语义
-
Old :内存布局兼容 C,可以安全
memcpy/memset,可与 C API 互操作
1. 平凡类型(Trivial Type)要求
一个类型要是"平凡的",必须满足:
✔ 没有用户自定义的构造、析构
(或者通过 = default 明确使用编译器生成的默认版本)
✔ 拷贝构造、移动构造是平凡的
(无用户自定义)
✔ 拷贝赋值、移动赋值是平凡的
(无用户自定义)
✔ 没有虚函数
✔ 没有虚基类
可通过:
cpp
std::is_trivial<T>::value
2. 标准布局类型(Standard Layout)要求
满足以下条件(只要违反任意一点,就不是 standard layout):
✔ 所有 非静态成员 的访问权限一致
要么全 public,要么全 private,要么全 protected。
✔ 继承满足特殊限制(避免复杂布局)
必须满足二选一:
-
子类有非静态成员 → 基类只能有"静态成员"
-
基类有非静态成员 → 子类不能再增加非静态成员
也就是说:
-
❌ 子类和基类都同时拥有非静态成员
-
❌ 多个基类都拥有非静态成员
这些都会破坏 standard layout。
✔ 第一个非静态成员的类型不能与基类类型相同
例如:
cpp
struct B {};
struct A : B { B b; }; // ❌ 非标准布局(第一个非静态成员 b 与基类同类型)
struct A2 : B { int x; B b; }; // ✔ 标准布局
✔ 没有虚函数或虚基类(与 trivial 一致)
✔ 成员和基类本身也必须是标准布局类型(递归要求)
可通过:
cpp
std::is_standard_layout<T>::value
最终的极简总结(放在笔记里最合适)
POD = trivial + standard layout
满足:
-
无用户定义构造/析构/拷贝/移动
-
无虚函数、无虚基类
-
成员访问权限一致
-
继承受严格限制(避免复杂内存布局)
-
成员和基类也必须是 POD/standard layout
-
内存布局兼容 C,可以安全 memcpy、memset
C++11 static_assert 编译期断⾔
static_assert(sizeof(void *) == 4,"64位系统不支持");
变量模板
变量模板变量模板让"模板常量"变得像模板函数一样好用,是现代模板元编程和 traits 编程的核心工具。
✅ 场景 1:按类型生成常量(最常见)
例如不同类型对应不同的 π 值、常量值。
cpp
template<typename T>
constexpr T pi_v = T(3.1415926535897932385);
int main() {
double a = pi_v<double>;
float b = pi_v<float>;
}
优点: 不用写 pi_float、pi_double 多个变量,直接 pi_v<T>。
✅ 场景 2:替代 enum,根据类型生成配置值
例如给不同类型定义 buffer 大小:
cpp
template<typename T>
constexpr size_t default_buffer_v = 64;
template<>
constexpr size_t default_buffer_v<double> = 128;
int main() {
static_assert(default_buffer_v<int> == 64);
static_assert(default_buffer_v<double> == 128);
}
优点: 配置可以根据类型专门定制(类似函数模板特化)。
✅ 场景 3:封装 type_traits(C++17 标准库也是这么做的)
你熟悉的:
-
std::is_same_v<T,U> -
std::is_integral_v<T> -
std::remove_const_t<T>
全是变量模板、别名模板。
你自己也可以做:
cpp
template<typename T>
constexpr bool is_string_v = false;
template<>
constexpr bool is_string_v<std::string> = true;
static_assert(is_string_v<std::string>);
static_assert(!is_string_v<int>);
比写 is_string<T>::value 简洁太多。
✅ 场景 4:根据类型提供某个特性值(policy)
比如你写数学库,不同类型拥有不同精度:
cpp
template<typename T>
constexpr T epsilon_v = T(0.001);
template<>
constexpr float epsilon_v<float> = 0.0001f;
template<>
constexpr double epsilon_v<double> = 1e-9;
bool equal(double a, double b) {
return std::abs(a - b) < epsilon_v<double>;
}
简单、可配置、可特化、类型相关。
✅ 场景 5:与 constexpr 一起用于编译期计算
例如编译期 FNV-1a 哈希:
cpp
template<size_t N>
constexpr uint32_t fnv1a_hash_v = (fnv1a_hash_v<N-1> ^ N) * 16777619u;
template<>
constexpr uint32_t fnv1a_hash_v<0> = 2166136261u;
static_assert(fnv1a_hash_v<10> != 0);
变量模板能承担"编译期常量数组"或"编译期状态机"作用。
✅ 场景 6:与概念 (C++20) 配合简化代码
cpp
template<typename T>
constexpr bool supports_print_v =
std::is_integral_v<T> || std::is_floating_point_v<T>;
template<typename T>
requires(supports_print_v<T>)
void print(T v) { std::cout << v; }
让概念表达式更清晰。
🚀 总结:变量模板的主要用途
| 场景 | 用途 |
|---|---|
| 类型相关常量 | pi_v、buffer_size_v |
| 配置特化 | 根据类型设置不同参数 |
| traits 简化 | is_xxx_v |
| policy 设计 | epsilon_v、precision_v |
| 编译期计算 | 哈希、序号生成 |
| 结合 concepts | 让约束更易读 |
整体来说,它让 "与类型绑定的常量值" 表达更加简单、优雅、易于特化。
聚合初始化和列表初始化
-
列表初始化(list initialization) :
C++11 引入的,用
{...}初始化对象的统一机制,包括所有{}初始化方式。 -
聚合初始化(aggregate initialization) :
某一类特殊类型(聚合类型) 使用{}时触发的 逐成员初始化规则。
| 特性 | 列表初始化 {} |
聚合初始化 |
|---|---|---|
| 范围 | 所有类型 | 只适用于聚合类型 |
| 是否调用构造函数 | 可能 | 不会 |
| 是否支持 narrowing | ❌ 不允许 | ❌ 不允许 |
| 初始化方式 | 构造函数 / initializer_list / 聚合 | 严格按成员顺序赋值 |
| 是否默认填充未给的值 | 依类型而定 | 是(value-init) |
C++17 折叠表达式(Fold Expression)总结
折叠表达式 是 C++17 为 可变参数模板(variadic templates) 引入的一种语法,用来在编译期展开参数包并应用操作符,让你不必写递归模板了。
1️⃣ 折叠表达式的语法模式
假设你有参数包 args... 和操作符 op:
一元折叠(Unary Fold)
只有一个操作符(前面或后面有 ...):
- 左折叠(Unary Left Fold):
(... op args) // ((arg1 op arg2) op arg3) ...
- 右折叠(Unary Right Fold):
(args op ...) // (arg1 op (arg2 op arg3)) ...
二元折叠(Binary Fold)
在折叠表达式左右各有一个初始值:
- 左折叠(Binary Left Fold):
(init op ... op args) // (((init op arg1) op arg2) op arg3) ...
- 右折叠(Binary Right Fold):
(args op ... op init) // (arg1 op (arg2 op (arg3 op init))) ...
"二元折叠"通常用于你希望提供一个初始值(identity element)。
2️⃣ 你的例子逐一分析
// 一元左折叠 template<typename... Args> auto sub_val_left(Args&&... args) { return (... - args); } auto t1 = sub_val_left(2,3,4); // ((2 - 3) - 4) = -5
-
只有
-,放在...右侧 → 左折叠 -
相当于:
((2-3)-4)
// 一元右折叠 template<typename... Args> auto sub_val_right(Args&&... args) { return (args - ...); } auto t2 = sub_val_right(2,3,4); // (2-(3-4)) = 3
-
只有
-,放在...左侧 → 右折叠 -
相当于:
2-(3-4)
// 二元左折叠 template<typename... Args> auto sub_one_left(Args&&... args) { return (1 - ... - args); } auto t3 = sub_one_left(2,3,4); // ((1-2)-3)-4 = -8
-
左边给了初始值
1→ 二元左折叠 -
展开顺序:
(((1-2)-3)-4)
// 二元右折叠 template<typename... Args> auto sub_one_right(Args&&... args) { return (args - ... - 1); } auto t4 = sub_one_right(2,3,4); // 2-(3-(4-1)) = 2
-
右边给了初始值
1→ 二元右折叠 -
展开顺序:
2-(3-(4-1))
3️⃣ 折叠表达式用途
-
代替递归模板,减少模板元编程复杂度
-
快速对参数包求和/乘积:
template<typename... Args> auto sum(Args... args) { return (... + args); } // 左折叠
- 逻辑操作(AND / OR):
template<typename... Args> bool all_true(Args... args) { return (... && args); } template<typename... Args> bool any_true(Args... args) { return (... || args); }
- 自定义组合运算(减法、异或、位运算等)
4️⃣ 可记忆口诀
| 折叠类型 | 语法 | 展开顺序 |
|---|---|---|
| 一元左折叠 | (... op args) |
(arg1 op arg2) op arg3 ... |
| 一元右折叠 | (args op ...) |
arg1 op (arg2 op arg3 ...) |
| 二元左折叠 | (init op ... op args) |
((init op arg1) op arg2) op ... |
| 二元右折叠 | (args op ... op init) |
arg1 op (arg2 op (... op init)) |
✅ 左折叠:从左到右
✅ 右折叠:从右到左
Concepts
概念 就是**给模板参数加"约束条件"**的机制。
在 C++20 之前,模板参数约束靠 enable_if 或 SFINAE,写法复杂,可读性差。
概念特点:
-
语法清晰:像写普通布尔表达式
-
提高可读性:出错信息更直观
-
限制模板参数类型:保证模板只接受符合条件的类型
核心思想:模板参数必须满足某个"概念"才能实例化
1️⃣ 定义概念(Concept)
cpp
#include <concepts>
#include <iostream>
// 定义一个概念:要求类型必须可加法
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>; // 约束加法结果类型也是 T
};
2️⃣ 使用概念约束模板参数
cpp
// 普通模板
template<Addable T> // 限定 T 必须满足 Addable
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(1, 2) << "\n"; // OK, int 可加
std::cout << add(1.5, 2.5) << "\n"; // OK, double 可加
// std::cout << add("hi", "world"); // ERROR, const char* 不满足 Addable
}
3️⃣ concepts 的简写方式
cpp
// 直接使用标准库概念
template<std::integral T> // T 必须是整型
T multiply(T a, T b) {
return a * b;
}
int main() {
std::cout << multiply(3, 4) << "\n"; // OK
// multiply(3.5, 4.2); // ERROR, double 不满足 std::integral
}
C++20 标准库自带很多概念,比如:
std::integral→ 整型
std::floating_point→ 浮点型
std::signed_integral→ 有符号整型
std::same_as<A,B>→ 类型完全相同
std::convertible_to<A,B>→ 可隐式转换
4️⃣ concepts 结合 requires 表达式
cpp
template<typename T>
requires std::integral<T> // 与模板参数绑定
T subtract(T a, T b) {
return a - b;
}
或直接放在函数签名里:
cpp
auto divide(std::floating_point auto a, std::floating_point auto b) {
return a / b;
}
✅ 总结
-
概念 = 模板参数的类型约束
-
语法清晰,读错误容易理解
-
可用自定义概念 或标准库概念
-
支持
requires表达式与auto限定 -
代替 SFINAE,现代 C++ 模板开发必备