C++ 高级

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。


✔ 继承满足特殊限制(避免复杂布局)

必须满足二选一:

  1. 子类有非静态成员 → 基类只能有"静态成员"

  2. 基类有非静态成员 → 子类不能再增加非静态成员

也就是说:

  • ❌ 子类和基类都同时拥有非静态成员

  • ❌ 多个基类都拥有非静态成员

这些都会破坏 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_floatpi_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️⃣ 折叠表达式用途

  1. 代替递归模板,减少模板元编程复杂度

  2. 快速对参数包求和/乘积

template<typename... Args> auto sum(Args... args) { return (... + args); } // 左折叠

  1. 逻辑操作(AND / OR):

template<typename... Args> bool all_true(Args... args) { return (... && args); } template<typename... Args> bool any_true(Args... args) { return (... || args); }

  1. 自定义组合运算(减法、异或、位运算等)

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;
}

✅ 总结

  1. 概念 = 模板参数的类型约束

  2. 语法清晰,读错误容易理解

  3. 可用自定义概念标准库概念

  4. 支持 requires 表达式与 auto 限定

  5. 代替 SFINAE,现代 C++ 模板开发必备

相关推荐
IMPYLH6 小时前
Lua 的 Coroutine(协程)模块
开发语言·笔记·后端·中间件·游戏引擎·lua
点云SLAM7 小时前
constexpr 和 explicit 在 C++ 中被提出的动机
开发语言·c++·explicit关键字·隐式转换·constexpr关键字·c++11/17/20
我命由我123457 小时前
python-dotenv - python-dotenv 快速上手
服务器·开发语言·数据库·后端·python·学习·学习方法
黑客思维者7 小时前
Python定时任务schedule/APScheduler/Crontab 原理与落地实践
开发语言·python·crontab·apscheduler
冷崖7 小时前
工厂模式-创建型
c++·设计模式
yaoxin5211237 小时前
268. Java Stream API 入门指南
java·开发语言·python
ss2737 小时前
ConcurrentLinkedQueue实战:电商秒杀系统的队列选型优化
java·开发语言·安全
qq_310658517 小时前
mediasoup源码走读(六)——NetEQ
服务器·c++·音视频
繁星蓝雨7 小时前
Qt优雅的组织项目结构三(使用CMakeLists进行模块化配置)——————附带详细示例代码
开发语言·数据库·qt