CppCon 2014 学习: C++11 in the Wild

article/2025/7/4 8:45:10

介绍了三个现代 C++ 的实用工具或功能。下面是对每部分的解释和总结:

1. Auto()

  • 用途:在作用域结束时自动执行清理代码,类似于 RAII 的机制。
  • 功能:你可以定义一段代码,在作用域结束时自动执行(不需要手动调用)。
  • 典型应用场景
    • 自动关闭文件
    • 自动解锁 mutex
    • 自动释放资源(比如临时分配的内存、数据库连接等)
Auto([]{ std::cout << "Leaving scope...\n"; });

这段代码就会在作用域结束时自动输出一句话。

2. make_iterable()iterator_range

  • 用途:将一对迭代器(begin 和 end)转换成类似容器的对象,可以用于范围-based for 循环。
  • 解决的问题
    • 通常,std::pair<iterator, iterator> 不能直接用于范围 for 循环。
    • 使用 make_iterable(begin, end) 可以更方便地遍历子范围。
auto v = std::vector<int>{1, 2, 3, 4, 5};
for (auto i : make_iterable(v.begin() + 1, v.end() - 1)) {std::cout << i << " ";
}
// 输出: 2 3 4

3. std::spaceship 操作符(三路比较符)

  • 用途:对两个对象进行统一的比较,自动生成 <, ==, >, !=, <=, >= 等操作符。
  • 特点
    • 使用 <=> 运算符,称为 三路比较运算符
    • 可以自定义返回值(比如 std::strong_ordering, std::partial_ordering 等)
    • 在结构体中一行定义即可取代所有比较函数。
struct Point {int x, y;auto operator<=>(const Point&) const = default;
};

这个例子中,Point 自动支持所有比较操作。

总结对照表:

功能名用途主要优点
Auto()在作用域结束时自动运行清理代码简化 RAII 风格,临时代码更方便
make_iterable()将一对迭代器包装为可遍历范围支持范围 for 循环遍历子范围
<=>(spaceship)自动生成比较运算符简洁高效,统一对象间的比较逻辑

这是关于 C++ 中作用域自动清理的一种改进方法,使用 Auto() 宏 来代替传统的手动清理逻辑,比如 OnScopeExit()

初始问题(传统做法)

void Mutate(State *state)
{state->DisableLogging();              // 关闭日志state->AttemptOperation();            // 执行某操作state->AttemptDifferentOperation();   // 执行另一操作state->EnableLogging();               // 恢复日志return;
}
问题:
  • 如果 AttemptOperation()AttemptDifferentOperation() 抛出异常,EnableLogging() 就永远不会被执行。
  • 手动恢复状态容易遗漏,尤其是代码路径复杂时。

改进方向:使用作用域清理机制(RAII)

使用像 Auto() 宏或 OnScopeExit() 这样的机制,可以确保在作用域退出时自动执行代码。

理想目标:改写为自动恢复的风格

void Mutate(State *state)
{state->DisableLogging();Auto([&] { state->EnableLogging(); }); // 作用域结束时自动启用日志state->AttemptOperation();state->AttemptDifferentOperation();return;
}

这个 Auto() 的行为:

  • 作用域开始:注册一段清理代码(lambda)
  • 作用域结束(正常 return 或异常):自动执行该 lambda
    类似于:
struct OnExit {std::function<void()> func;~OnExit() { func(); }
};

这样做的优点:

优点描述
更安全不怕中途 return 或异常跳过清理代码
更清晰清理逻辑靠近资源分配逻辑,语义清楚
更简洁避免写繁琐的 try/catch 或手动清理代码
RAII 风格一致与 C++ 的资源管理惯例一致

你现在展示的是一个 改进版的作用域退出执行机制 ——使用 Auto(...) 宏,在作用域结束时自动执行一段代码(无论是正常返回、错误返回,还是抛出异常),从而确保资源释放或状态恢复不会遗漏。

原始代码的问题(没有清理)

bool Mutate(State *state)
{state->DisableLogging();                         // 日志被关闭if (!state->AttemptOperation()) return false;    // 可能提前 returnif (!state->AttemptDifferentOperation()) return false;state->EnableLogging();                          // 可能不会执行!return true;
}

如果中途 return falseEnableLogging() 就不会执行 —— 逻辑错误。

想要的写法(使用 Auto() 宏)

bool Mutate(State *state)
{state->DisableLogging();Auto(state->EnableLogging());   // 确保作用域退出时自动恢复日志if (!state->AttemptOperation()) return false;if (!state->AttemptDifferentOperation()) return false;return true;
}

无论函数是走完逻辑还是中途返回/异常,都保证执行 state->EnableLogging(),从而让代码更安全、清晰。

实现分析

AtScopeExit
template <class Lambda> class AtScopeExit {Lambda& m_lambda;
public:AtScopeExit(Lambda& action) : m_lambda(action) {}~AtScopeExit() { m_lambda(); }
};
  • 用于在作用域退出时执行传入的 lambda。
  • RAII 原则:构造时捕获清理动作,析构时自动调用。
Auto() 宏展开过程(简化理解)
Auto(state->EnableLogging());

最终变成:

auto Auto_func_42 = [&]() { state->EnableLogging(); };
AtScopeExit<decltype(Auto_func_42)> Auto_instance_42(Auto_func_42);
  • __COUNTER__ 确保生成的变量名唯一,防止宏冲突。
  • Auto() 用于注入作用域清理代码。

总结优点

优点描述
安全不会遗漏清理动作,无论发生什么提前退出
简洁一行代码写清清理逻辑
通用性强可用于文件关闭、锁释放、状态还原等场景
不依赖 C++17在 C++11 及以上即可使用
如你所说,异常控制流也存在同样问题,而 Auto() 机制能一致地解决所有“作用域退出”问题,推荐用于关键资源的管理。
是否还需要我:
  • 举例更多使用场景?
  • 帮你实现跨多个资源清理?
  • 对比 C++17 的 std::scope_exit

你展示的是一个用于 自动作用域退出清理 的 C++ 宏和模板机制 —— 本质是手动实现了 scope guard(类似于 C++17 的 std::scope_exit),并通过宏包装使使用更简洁。

目标:让你能写出一行这样的代码

Auto(state->EnableLogging());

自动在作用域结束时执行 state->EnableLogging(),无论是正常退出还是提前 return 或抛出异常。

宏和类组件解析

AtScopeExit 模板类
template <class Lambda>
class AtScopeExit {Lambda& m_lambda;
public:AtScopeExit(Lambda& action) : m_lambda(action) {}~AtScopeExit() { m_lambda(); } // 自动在作用域结束时执行 lambda
};
  • 持有 lambda 引用,在析构时执行它
  • 实现的是 RAII(Resource Acquisition Is Initialization)模式
  • 核心行为:作用域结束 = 执行 cleanup 逻辑
② 宏魔法层层展开
Auto(...) 最终展开成:
Auto(...) 
// expands into
Auto_INTERNAL2(__COUNTER__, ...)
// expands into
Auto_INTERNAL1(Auto_func_42, Auto_instance_42, ...)
// expands into:
auto Auto_func_42 = [&]() { ... };
AtScopeExit<decltype(Auto_func_42)> Auto_instance_42(Auto_func_42);

每一层负责一部分功能:

功能
TOKEN_PASTE(x, y)拼接 x##y,生成唯一变量名
Auto_INTERNAL1(...)声明 lambda 和 AtScopeExit 对象
Auto_INTERNAL2(...)__COUNTER__ 自动生成唯一 ID
Auto(...)用户接口,一行搞定

优点总结

特性描述
安全避免忘记清理资源或状态恢复
通用可用于锁、文件、事务、状态切换等
简洁一行代码,无需显式手动 try/finallycatch
无依赖不依赖 C++17,兼容 C++11
宏唯一性__COUNTER__ 保证多个 Auto() 调用间不冲突

使用场景示例

void doSomething() {lock.lock();Auto(lock.unlock());  // 自动释放锁file.open();Auto(file.close());   // 自动关闭文件timer.start();Auto(timer.stop());   // 自动停止计时器
}

如果你使用 C++17 或更新标准,也可以使用标准库的:

#include <scope>
std::scope_exit guard{ [&] { state->EnableLogging(); } };

不过当前这种 Auto(...) 宏实现非常轻量、兼容性好,适用于 C++11 项目。
如果你还想:

  • 加强这个宏对异常的捕捉能力?
  • Auto(...) 添加调试输出?
  • 抽象出多个资源同时清理?

完整地解释这一宏的全部展开过程,逐步拆解你写的:

#define Auto(...) Auto_INTERNAL2(__COUNTER__, __VA_ARGS__)

宏定义流程逐层解释

这是一组用于作用域退出自动执行的宏展开链。以下是从 Auto(...) 开始的完整过程:

用户写:

Auto(state->EnableLogging());

② 展开:

Auto_INTERNAL2(__COUNTER__, state->EnableLogging());

假设 __COUNTER__ 当前是 42,那么变成:

Auto_INTERNAL2(42, state->EnableLogging());

Auto_INTERNAL2 定义是:

#define Auto_INTERNAL2(ctr, ...) \Auto_INTERNAL1(TOKEN_PASTE(Auto_func_, ctr), \TOKEN_PASTE(Auto_instance_, ctr), __VA_ARGS__)

代入 ctr = 42

Auto_INTERNAL1(Auto_func_42, Auto_instance_42, state->EnableLogging());

Auto_INTERNAL1 定义是:

#define Auto_INTERNAL1(lname, aname, ...) \auto lname = [&]() { __VA_ARGS__; }; \AtScopeExit<decltype(lname)> aname(lname);

代入 lname = Auto_func_42, aname = Auto_instance_42, __VA_ARGS__ = state->EnableLogging()

auto Auto_func_42 = [&]() { state->EnableLogging(); };
AtScopeExit<decltype(Auto_func_42)> Auto_instance_42(Auto_func_42);

最终展开的 C++ 代码:

auto Auto_func_42 = [&]() { state->EnableLogging(); };
AtScopeExit<decltype(Auto_func_42)> Auto_instance_42(Auto_func_42);

这两行在当前作用域中创建了一个 lambda 和其管理对象。当当前作用域结束时(不管是正常 return、异常还是其他),Auto_instance_42 析构,自动调用 state->EnableLogging()

全套宏定义总结(完整)

#pragma once
template <class Lambda>
class AtScopeExit {Lambda& m_lambda;
public:AtScopeExit(Lambda& action) : m_lambda(action) {}~AtScopeExit() { m_lambda(); }
};
// 连接两个 token
#define TOKEN_PASTEx(x, y) x ## y
#define TOKEN_PASTE(x, y) TOKEN_PASTEx(x, y)
// 生成作用域退出对象
#define Auto_INTERNAL1(lname, aname, ...) \auto lname = [&]() { __VA_ARGS__; }; \AtScopeExit<decltype(lname)> aname(lname);
// 使用 __COUNTER__ 保证唯一性
#define Auto_INTERNAL2(ctr, ...) \Auto_INTERNAL1(TOKEN_PASTE(Auto_func_, ctr), \TOKEN_PASTE(Auto_instance_, ctr), __VA_ARGS__)
// 用户使用的简化宏
#define Auto(...) Auto_INTERNAL2(__COUNTER__, __VA_ARGS__)

让我们逐条深入理解这些概念,它们是构建 Auto() 宏的关键组成部分:

核心技术解说

1. Variadic macros & __VA_ARGS__(可变参数宏)

#define LOG(...) printf(__VA_ARGS__)
  • __VA_ARGS__ 允许宏接受不定数量的参数。
  • Auto(...) 宏中,它传入要在作用域结束时执行的任意代码语句(如 state->EnableLogging();)。
    用途:构建灵活、复用性高的宏。

2. Token pasting (##)

#define COMBINE(x, y) x ## y
  • 将两个 token 拼接为一个标识符。例如:COMBINE(Auto_func_, 42)Auto_func_42
  • 用于生成唯一变量名,避免命名冲突。
    用途:宏生成局部变量名时防止重复。

3. Templates

template <typename Lambda>
class AtScopeExit {Lambda& m_lambda;~AtScopeExit() { m_lambda(); }
};
  • 泛型类允许我们在编译时确定 lambda 类型。
  • 相比虚函数,模板无运行时开销(零开销抽象)。
    用途:为 Auto() 提供泛型作用域结束行为执行器。

4. Lambdas (C++11)

auto f = [&]() { ... };
  • 匿名函数对象,可捕获作用域变量。
  • Auto() 中,lambda 捕获局部上下文,确保代码在作用域结束时执行。
    用途:简洁、高效的行为表达方式。

5. __COUNTER__(非标准,但广泛支持)

  • 每次展开宏时生成一个递增的整数字面量。
__COUNTER__ // → 0, 1, 2, ...
  • 与 token pasting 结合可生成唯一变量名。
    用途:自动避免宏多次展开导致的命名冲突。

6. #pragma once(非标准,但实际标准)

#pragma once
  • 防止头文件被多重包含。
  • 非标准,但被所有主流编译器支持(GCC、Clang、MSVC)。
    用途:更简洁的 include guard 替代方式。

风格问题(Style Points)

7. “Aren’t macros evil?”

Yes, but context matters.

  • 宏缺少类型检查和作用域控制,会导致调试困难。
  • 但像 Auto() 这种“局部作用域 + 宏生成 + 模板”组合,是 “好宏”的范例
    • 不污染全局命名空间。
    • 没有副作用或隐藏控制流。
    • 在编译期展开并优化。
      最佳实践:谨慎使用宏,能用 constexpr 或模板就别用宏。但 Auto() 这样功能清晰且不可替代的用途是 OK 的。

8. Why lambdas instead of std::function?

Lambda 更轻量,性能更高。

  • std::function<void()> 是类型擦除,需要动态分配(在某些实现中),开销较大。
  • Lambda 可在编译期内联,没有额外开销。
  • AtScopeExit 只需要存储 lambda 本身,不需要多态性 → 模板就足够。
    结论使用 lambda 更快、更轻、更符合用途,尤其在作用域控制工具中非常合适。

总结

技术用途
__VA_ARGS__灵活支持任意数量代码语句
##动态生成变量名,防止冲突
模板泛型支持不同 lambda 类型,零开销抽象
Lambda高效、简洁地封装作用域结束动作
__COUNTER__为变量生成唯一编号
#pragma once替代 include guard
风格观点此场景下宏设计是安全且清晰的

提供的内容讨论了两个非常实用但“非标准”的编译器特性:__COUNTER__#pragma once,并顺带解释了“幂等性(idempotence)”的含义。我们逐一理解:

__COUNTER__ 的含义与用途

#define UNIQUE_NAME(x) x##__COUNTER__
  • __COUNTER__ 是一个 每次展开都会递增的整数宏
  • 非标准,但 所有主流编译器(如 GCC、Clang、MSVC)都支持
  • 主要用于 宏展开中自动生成唯一变量名,防止命名冲突。
    例如:
int var_##__COUNTER__;  // 展开为:int var_0;
int var_##__COUNTER__;  // 展开为:int var_1;

你提到 __LINE__ 作为替代:
确实,它也是预定义宏,每次展开为当前行号。
但如果多个宏定义在同一行,则 __LINE__ 无法生成唯一标识符,而 __COUNTER__ 可以 → 所以不等价也不可靠

#pragma once 与幂等性

  • #pragma once 是防止头文件被多次包含的最简单方式。
  • 它使文件具有 幂等性(idempotence):无论包含多少次,只会生效一次。
    替代方式是传统的:
#ifndef MY_HEADER_H
#define MY_HEADER_H
// content...
#endif

这种方式虽然标准,但容易出错(特别是宏名重复),也更冗长。
#pragma once 优点:

  • 简洁
  • 不需要命名唯一宏
  • 编译器能更快处理
    你提到:

“它是非标准的,但我说它是每个编译器都支持的。”

这是一个很常见的技术现实 —— 尽管不是 C++ 标准文档的一部分,但所有主流实现都支持,实际上已成“事实标准”。

幂等性(Idempotence)的定义与解释

“一个函数 f 是幂等的,当 f(f(x)) = f(x) 对所有 x 成立。”

  • 在软件工程中,幂等性常用于描述 多次调用的副作用是否相同
  • 对于头文件,如果多次包含它的结果和只包含一次一样,那就具有幂等性。
  • #pragma once 保证头文件只处理一次 → 是构建幂等头文件的最有效方式。

小结

特性是什么是否标准是否安全用
__COUNTER__编译期间递增的整数宏,用于唯一命名是“事实标准”
#pragma once确保头文件只包含一次,实现幂等性非常推荐
幂等性多次调用等价于一次调用数学概念理论支撑
如你想进一步了解:
  • __COUNTER__ 在大型宏系统中的实际应用(如测试框架)
  • #pragma once 与 include guard 的性能对比
  • 或是幂等性在 REST API / 数据库操作中的作用

你正在理解一个非常典型的 性能优化决策:为什么 Auto() 宏背后的 AtScopeExit 类没有用 std::function

让我们一条条解释清楚:

问题核心:为什么不用 std::function

先看两种实现的区别:

我们使用的轻量实现

template <class Lambda>
class AtScopeExit {Lambda& m_lambda;
public:AtScopeExit(Lambda& action) : m_lambda(action) {}~AtScopeExit() { m_lambda(); }
};
  • 不依赖 <functional>
  • 不涉及类型擦除。
  • 没有动态分配(heap allocation)。
  • 完全零成本抽象,编译器能 inline 展开整个 lambda 逻辑。

std::function<void(void)> 会怎样?

#include <functional>
class AtScopeExit {std::function<void()> m_lambda;
public:template <class Lambda>AtScopeExit(Lambda& action) : m_lambda(action) {}~AtScopeExit() { m_lambda(); }
};

为何不选它?理由如下:

🔹 1. 重量级头文件 <functional>

  • std::function 需要引入 <functional>,它本身非常庞大,带来不必要的编译时间和依赖开销。
  • auto.h 被频繁包含、甚至被生成工具包含 → 需要保持轻量,避免拖慢整个构建系统

🔹 2. 类型擦除 + 可能的堆分配

  • std::function 采用 类型擦除(type erasure)机制,把任意可调用对象封装成一个统一的函数对象。
  • 这通常意味着 内部堆分配(heap allocation),尤其是在闭包很大或不能放进 small-buffer-optimization 时。
    这种开销在小函数作用域中使用时是 完全不值得的,而 lambda 原生形式在编译期就能优化为直接调用。

🔹 3. 不可 inline 展开,影响性能

  • std::function 的调用是虚拟调用(或函数指针间接调用)。
  • 编译器无法 inline 内部逻辑,导致性能下降。
    反之,模板方式保留了具体类型信息,编译器可以把 lambda 完全 inline 展开,优化得更彻底(零开销)。

经验数据支持这个决策(即:“我们看到汇编更好”)

“Empirically, we get better code this way.”

他们查看了汇编输出,发现:

  • 使用 std::function:多了堆分配、间接调用,调用开销大。
  • 使用模板 + lambda 引用:代码更短、更快,全程 inline。

小结:为什么不用 std::function

原因解释
1. 编译依赖大<functional> 是重量级头文件,会拖慢编译速度
2. 类型擦除和堆分配std::function 可能动态分配内存,带来运行时开销
3. 无法内联编译器无法展开 std::function 中的 lambda,性能较差
4. 实际测试更慢汇编验证显示模板写法生成的机器码更紧凑、更快
5. 要保持宏轻量Auto() 是通用宏,需要可插拔、轻量、高效

代码回顾:

#include <stdio.h>
#include "auto.h"        // 假设提供了 Auto(...) 宏
extern void foo();       // 声明外部函数 foo()
int main() {if (true) {Auto(puts("two"));puts("one");          // 编译器知道这不会抛出异常}if (true) {Auto(puts("three"));foo();                // 可能抛出异常}
}

分析:

1. Auto(...) 是什么?

这是一个宏,看起来像是模拟 RAII(Resource Acquisition Is Initialization) 风格,用于自动执行某段代码,通常是在作用域结束时。
这类似于 C++ 的 defer 模拟,或者 Go 语言里的 defer 语句。C 本身不支持这一特性,但很多实现里通过宏和 __attribute__((cleanup)) 机制或构造函数/析构函数模拟这一行为。
假设用途: Auto(expr) 会在当前作用域退出时执行 expr。但这里,它看起来是立即执行的。
例如:

Auto(puts("two"));  // 马上打印 "two",并注册一个作用域结束时的清理动作?
2. 实际执行顺序:
if (true) {Auto(puts("two"));  // 立即打印 "two"puts("one");        // 然后打印 "one"
}
// 输出顺序:
// two
// one
if (true) {Auto(puts("three"));  // 打印 "three"foo();                // 如果 foo 抛出异常(如 longjmp、signal),Auto 注册的清理动作可能不执行
}

注意这里对异常的含义较宽泛:C 本身没有异常处理,但某些 C 代码会使用 setjmp/longjmp、信号、甚至是某种内嵌 C++ 异常机制。

总结:

  • Auto(...) 可能是一个自定义的宏,模仿 RAII,用于作用域结束时自动执行某些操作。
  • puts("two")puts("three") 是立即执行的,但也可能被注册为作用域退出时执行的动作(看具体宏定义)。
  • 如果 foo() 抛出异常或导致非正常跳出,Auto 注册的代码可能不会被执行,除非宏做了异常安全处理。
    在这里插入图片描述

离开作用域执行

当然,我们全面解释一遍你提到的 std::function 的底层实现机制,以及为什么它性能较差。

std::function 是什么?

std::function<void()> 是一个可以存储任意可调用对象(函数指针、Lambda、函数对象、绑定表达式等)并调用它的通用类型。
它的本质是通过 类型擦除(Type Erasure) 实现的。

类型擦除的实现方式

你给出的示例代码基本就是 std::function 的简化实现版本。我们来逐步理解它的实现思想:

1. 一个抽象的接口 ContainerBase

struct ContainerBase {virtual void perform() = 0;virtual ~ContainerBase() = default;
};

这是一个抽象基类,用于统一接口,隐藏具体 Lambda 类型。

2. 对任意类型的 Lambda 进行包装

template <class Lambda>
struct Container : ContainerBase {Lambda m_lambda;Container(Lambda&& lambda) : m_lambda(std::move(lambda)) {}void perform() override { m_lambda(); }
};

这个模板类可以包装任何类型的 Lambda,继承 ContainerBase,并实现了虚函数 perform()

3. 类似 std::function<void()> 的封装类

class function {ContainerBase* m_ctr;
public:template<class Lambda>function(Lambda lambda): m_ctr(new Container<Lambda>(std::move(lambda))) {}void operator()() { m_ctr->perform(); }~function() { delete m_ctr; }
};
  • 构造时进行 类型擦除:不再关心 Lambda 的真实类型。
  • 调用时通过 virtual 来执行 perform()
  • 内部使用 new 分配内存存放 Lambda。

为什么性能差?

这段代码展示了 std::function 性能瓶颈的 3 大核心来源

1. 虚函数调用(Virtual Dispatch)

m_ctr->perform();
  • 是一个间接调用,不能被内联。
  • 会导致 CPU 分支预测失败,降低执行效率。

2. 堆内存分配(Heap Allocation)

new Container<Lambda>(...)
  • 每次构造 function 时,都在堆上分配一个包装对象。
  • 内存分配/释放很慢,尤其在频繁创建销毁的场景下。

3. 移动构造 Lambda

std::move(lambda)
  • 虽然是“移动”,但也会调用 Lambda 的移动构造函数,进而移动其所有捕获变量。
  • 如果 Lambda 捕获较多东西,移动成本仍然存在。

🛠 小函数优化(Small Function Optimization, SFO)

有些 std::function 实现会做小对象优化,即:

  • 如果 Lambda 很小(比如 1-2 个指针大小),就直接存在 std::function 内部,不使用堆内存。
  • 这样可以避免 heap allocation。
    但并不是所有实现都有 SFO,或启用条件不同。

更高效的替代方案

你还提到了其他更高效的替代方式:

1. Alexandrescu 的 ScopeGuard

template<typename F>
struct ScopeGuard {F func;ScopeGuard(F f) : func(std::move(f)) {}~ScopeGuard() { func(); }
};
  • 没有虚函数
  • 没有堆内存
  • 完全内联,高效!
    用法:
auto guard = ScopeGuard([]{ puts("cleaning up"); });

2. Boost.ScopeExit / Google scope_exit

这些库提供更方便的宏或语法糖实现,比如:

BOOST_SCOPE_EXIT(...) { /* 资源释放代码 */ } BOOST_SCOPE_EXIT_END

或 C++17:

auto cleanup = gsl::finally([] { std::cout << "done\n"; });

这些方式都是基于模板、无虚函数、无堆分配,更适合资源管理、性能敏感的场景

总结表格

特性std::functionScopeGuard / finally
支持任意类型的函数
虚函数调用
堆内存分配
可内联(inline)
适合频繁调用/构造
性能一般

Type Erasure in a nutshell”:

Type Erasure(类型擦除)简明理解

背景:

C++ 是静态类型语言,通常要求在编译时就知道对象的具体类型。但有时我们想要写出可以处理“任意类型”对象的代码,比如:

  • std::function
  • std::any
  • std::variant
  • typeid / void* 等用途
    为了实现这种“隐藏真实类型”的能力,我们引入了 类型擦除技术(Type Erasure)

第一步:使用模板类封装任意类型对象

template<typename T>
class Container {T captured_object;
};

这个类可以持有任何类型的对象,比如:

Container<int> ci;         // 存 int
Container<std::string> cs; // 存 string
Container<MyClass> cm;     // 存自定义类型

问题:
虽然 Container<T> 是泛型的,但它每个类型都不同Container<int>Container<std::string>),我们不能把这些放进一个容器或指针中统一处理。

目标:创建一个可以“处理任意类型”的统一接口

比如:

std::vector<TypeErasedObject> v;
v.push_back(42);
v.push_back("hello");
v.push_back(MyClass());

第二步:为任意 Container 提供统一接口(通过继承虚函数)

你可以这么做:

struct ContainerBase {virtual void perform() = 0;virtual ~ContainerBase() = default;
};
template<typename T>
class Container : public ContainerBase {T captured_object;
public:Container(T obj) : captured_object(std::move(obj)) {}void perform() override {// 调用 T 的接口,如 operator()、print()、serialize() 等captured_object();  // 假设 T 是个可调用对象}
};

这时,虽然 Container<T> 是模板类,但它们都可以被转换成 ContainerBase* 指针

ContainerBase* p1 = new Container<int>(42);      // ok
ContainerBase* p2 = new Container<MyLambda>(...); // ok

你就完成了类型擦除的关键:

编译时知道类型 → 运行时隐藏类型

通过:

  • 模板封装具体类型
  • 虚函数统一访问接口
  • 基类指针存储/调用

总结一句话

“类型擦除”就是:使用模板捕获任何类型,然后通过指向基类的指针在运行时操作它。

如果你想更深入了解:

  • std::function 就是这个套路的完整演示(可调用类型擦除)
  • std::any 用类似原理做任意类型持有(带 RTTI)
  • std::shared_ptr<void> 也利用了部分类型擦除思想
#pragma once
#include <utility>
struct ContainerBase {virtual void perform() = 0;virtual ~ContainerBase() = default;
};
template <class Lambda>
struct Container : ContainerBase {Lambda m_lambda;Container(Lambda&& lambda) : m_lambda(std::move(lambda)) {}virtual void perform() { m_lambda(); }
};
class function {  // equivalent to std::function<void(void)>ContainerBase* m_ctr;
public:template <class Lambda>function(Lambda lambda) : m_ctr(new Container<Lambda>(std::move(lambda))) {}void operator()() { m_ctr->perform(); }~function() { delete m_ctr; }
};

目标回顾

我们想要一个 function 类,能够像 std::function<void()> 一样:

  • 存储任意可调用对象(比如 lambda、函数、函数对象等)
  • 调用统一接口 operator()()
  • 释放时能正确析构

关键组件逐个解释

ContainerBase:虚基类接口

struct ContainerBase {virtual void perform() = 0;virtual ~ContainerBase() = default;
};

作用: 所有持有对象都将派生自它,提供统一的 perform() 接口。

Container<T>:包装任何可调用对象的模板类

template <class Lambda>
struct Container : ContainerBase {Lambda m_lambda;Container(Lambda&& lambda) : m_lambda(std::move(lambda)) {}virtual void perform() override { m_lambda(); }
};

说明:

  • 这是一个模板类,支持包装任意类型 Lambda(只要它是 callable)
  • 调用 perform() 时,它就执行 m_lambda() —— 即用户给的 lambda 或函数对象
  • m_lambda 是通过 移动构造 接收的,提高性能

function:类型擦除封装器(相当于 std::function<void()>

class function {ContainerBase* m_ctr;
public:template<class Lambda>function(Lambda lambda): m_ctr(new Container<Lambda>(std::move(lambda))) {}void operator()() { m_ctr->perform(); }~function() { delete m_ctr; }
};

功能完整实现:

成员作用
m_ctr指向 ContainerBase,实际类型为 Container<Lambda>
构造函数接收任意 Lambda,并用 new 创建对应 Container<Lambda> 实例
operator()()调用 perform(),等效于执行原始 lambda
析构函数删除容器,释放内存

举个例子:

function f = [] { puts("Hello Type Erasure!"); };
f(); // 输出:Hello Type Erasure!

运行流程:

  1. [] { puts(...) } 被作为 Lambda 捕获
  2. new Container<Lambda>(...) 构建实际容器
  3. m_ctr->perform() 实际调用 lambda

类型擦除的关键要素(总结)

元素解释
模板类捕获任意类型(如 Container<T>
虚基类提供统一接口(如 ContainerBase::perform
基类指针存储实际对象(如 ContainerBase*
动态分配避免模板类型“污染”使用方,靠堆上存储
虚函数实现运行时行为调用

注意点:

  • 每次使用 function 都会 堆分配 一个新对象 → 性能低于 lambda
  • 每次调用都要 虚函数调度 → 比内联调用慢
  • 适用于需要 类型擦除 的场景,如:回调、事件注册、异步任务等

std::function 的关系

std::function<void()> 的实现原理和这个 function 几乎一样,只是:

  • std::function 还做了 Small Buffer Optimization(小对象不堆分配)
  • std::function 支持拷贝构造、赋值、空检查等

总结一句话

这段代码就是一个最小实现的 std::function<void()>,它使用类型擦除技术,让你能把“任意可调用对象”封装为一个可以调用的统一接口。

使用 std::function 或类似类型擦除方案时涉及的性能成本

1. 虽然 std::move 在运行时是零成本(只是一个类型转换),但它在编译时会触发复杂的模板元编程逻辑,比如:

  • std::remove_reference
  • std::is_rvalue_reference
  • std::decay
  • std::enable_if
    这些模板机制对编译器是有一定负担的,尤其在大项目中可能导致编译时间变慢

2. 类型擦除实现用到了虚函数(如 virtual void perform()),这会导致:

  • 通过 vtable(虚函数表) 进行函数调用
  • 这种调用方式相比普通函数指针或内联代码,慢一些
  • 特别在频繁调用时,性能差异明显(如高性能场景:图形、音频、计算)
    大多数 std::function 实现,在类型擦除时需要:
  • new 一个 Container<Lambda> 对象
  • 这会在堆上动态分配内存,速度慢 + 容易内存碎片
    但如果你传入的 lambda 很小,比如只捕获一个 int 引用,那么:
  • 编译器可能使用 “小对象优化” (SBO, Small Buffer Optimization)
  • 即在 std::function 对象内部直接存储 lambda,避免堆分配
    这就像 std::string 的小字符串优化:短字符串不在堆上分配。

3. 你传给 std::functionlambda

auto f = [x, &y] { ... };

在封装成 functionContainer<Lambda> 时,会发生:

  • Lambda 的 移动构造
  • 所有捕获的变量也会随着一起被移动
    如果你捕获的是一个大的对象(如:大数组、大字符串等),移动可能代价高。
    但如果你是按引用捕获(如 &x),那么:
  • 移动构造成本几乎为 0
  • 因为只是复制一个指针

总结(:

| ------------------------------- |
| std::move 编译期会触发复杂模板机制,影响编译速度 |
| 虚函数表调度导致运行时性能开销 |
| 堆内存分配开销大,可能影响性能 |
| 如果 lambda 很小,可用小对象优化,避免堆分配 |
| 封装 lambda 时一定会 move 构造,影响捕获变量 |
| 如果捕获的是引用,move 成本很低 |

template <class Lambda>
class AtScopeExit {Lambda& m_lambda;
public:AtScopeExit(Lambda& action) : m_lambda(action) {}~AtScopeExit() { m_lambda(); }
};
#define TOKEN_PASTEx(x, y) x##y
#define TOKEN_PASTE(x, y) TOKEN_PASTEx(x, y)
#define Auto_INTERNAL1(lname, aname, ...) \auto lname = [&]() { __VA_ARGS__; };  \AtScopeExit<decltype(lname)> aname(lname);
#define Auto_INTERNAL2(ctr, ...) \Auto_INTERNAL1(TOKEN_PASTE(Auto_func_, ctr), TOKEN_PASTE(Auto_instance_, ctr), __VA_ARGS__)
#define Auto(...) Auto_INTERNAL2(__COUNTER__, __VA_ARGS__)

C++ 类 MDTable,它代表了一个表格结构(可能类似数据库的表),其中的“列”和“键”是通过原始指针存储的。

这段代码定义了什么?

class MDTable {MDColumn* m_columns;MDKey* m_keys;int m_columnCount;int m_keyCount;
public:MDColumn* GetColumns() const { return m_columns; }int GetNumColumns() const { return m_columnCount; }MDIndex* GetKeys() const { return m_keys; }int GetNumKeys() const { return m_keyCount; }
};

成员变量说明:

成员变量含义
m_columns指向 MDColumn 数组的指针(即所有列)
m_keys指向 MDKey 数组的指针(即所有键)
m_columnCount列的数量
m_keyCount键的数量

成员函数说明:

函数功能
GetColumns()返回列数组的指针
GetNumColumns()返回列的数量
GetKeys()返回键数组的指针
GetNumKeys()返回键的数量

这类设计为何被称为 “Inside-Out Container”?

Inside-out container” 是一种设计模式,在这种设计中:

  • 容器类本身(这里是 MDTable并不自己管理元素(比如使用 std::vector),而是暴露出“裸指针 + 数量”来让使用者自行访问。
  • 所有数据都暴露为原始数组,你要通过 GetColumns()GetNumColumns() 来访问元素。
    这种设计常用于:
  • 高性能场景(避免 STL 带来的拷贝、构造开销)
  • 与 C 接口兼容
  • 结构紧凑,占用少量内存
    但也有缺点:
  • 不安全(容易越界)
  • 不现代(不支持 range-based for)
  • 不好维护(不够封装)

可能的改进方向:make_iterable

为了让这类结构能像现代 C++ 一样支持:

for (auto& col : table.GetColumns()) { ... }

我们可以构建一个工具,比如 make_iterable(begin_ptr, count),让 MDTablestd::vector 一样支持迭代器。
例如:

template<typename T>
struct IterableFromPointer {T* m_ptr;int m_size;T* begin() const { return m_ptr; }T* end() const { return m_ptr + m_size; }
};
template<typename T>
IterableFromPointer<T> make_iterable(T* ptr, int size) {return {ptr, size};
}

用法就变成这样:

for (auto& col : make_iterable(table.GetColumns(), table.GetNumColumns())) {// 使用 col
}

总结:

内容
类名MDTable
核心思想裸数组 + 元素数量 的容器结构
特点高效但不安全,不符合现代 C++ 审美
可改进点使用 make_iterable 创建迭代器支持,更安全、更现代

对比传统 C 风格接口(裸指针 + 计数)和现代 C++ 风格(如 std::vector 和 range-based for 循环)的优缺点,并在此基础上提出了一个更现代、优雅的改进方式:通过包装工具函数如 Columns(tab)Keys(tab),让代码更简洁、更安全。

为什么不直接用 std::vector

当前的设计(裸指针):

class MDTable {MDKey* m_keys;              // 同时保存普通键和外键int m_keyCount;             // 键总数int m_firstForeignKey;      // 外键在数组中的起始索引
public:MDIndex* GetNormalKeys() const { return m_keys; }int GetNumNormalKeys() const { return m_firstForeignKey; }MDIndex* GetForeignKeys() const { return m_keys + ...; }int GetNumForeignKeys() const { return m_keyCount - ...; }
};

为什么不这样写?

std::vector<MDKey> m_keys;

因为这样设计是出于效率和内存布局考虑

原因总结:

原因说明
时间效率裸指针访问和按索引访问没有边界检查,比 std::vector::at()
空间效率避免了 std::vector 的额外容量(capacity)或元数据开销
内存控制可以将所有键(普通键 + 外键)存储为一个连续块,节省分配次数
C 接口兼容性裸指针在与 C 函数、旧代码、内存映射文件打交道时更方便
所以虽然 std::vector 更方便、更安全,但在一些对性能极度敏感或者需要内存映射/兼容旧代码的系统中,裸指针 + 数量仍被广泛使用。

这种用法很笨拙

使用方式如下:

for (int i = 0; i < tab->GetNumColumns(); ++i) {MDColumn& col = tab->GetColumns()[i];// ... 处理 col ...
}

对程序员要求较高,容易越界,写法也冗长。

我们理想中想写的方式:

for (MDColumn& col : Columns(tab)) {// ... 处理 col ...
}

这就是现代 C++ 的风格 —— 使用 range-based for 来遍历容器,无需关心长度和索引细节

解决方案:自定义可迭代包装器

你可以写一个类似 make_iterable() 的函数,让这些裸指针接口也能用 for (auto& x : ...) 方式遍历。
例如:

template<typename T>
struct PointerRange {T* m_ptr;int m_size;T* begin() const { return m_ptr; }T* end() const { return m_ptr + m_size; }
};
PointerRange<MDColumn> Columns(MDTable* tab) {return {tab->GetColumns(), tab->GetNumColumns()};
}
PointerRange<MDKey> Keys(MDTable* tab) {return {tab->GetKeys(), tab->GetNumKeys()};
}

使用效果就很舒服了:

for (auto& col : Columns(tab)) { ... }
for (auto& key : Keys(tab)) { ... }

总结

项目原始方式现代方式
可读性较差(手写索引)极好(range-for)
安全性易越界可封装检查
性能极致性能可控稍有代价(可忽略)
扩展性

用一个简单的“可迭代包装器” (iterable) 把原始的裸指针数组包装起来,从而可以使用 C++11 的范围 for 循环(range-based for loop)来访问数组元素。

逐行解释

#include "iterable.h"

引入一个 iterable 类型和 make_iterable() 函数的定义。这个头文件里大概定义了:

template<typename Iterator>
struct iterable {Iterator m_begin, m_end;Iterator begin() const { return m_begin; }Iterator end() const { return m_end; }
};
template<typename Iterator>
iterable<Iterator> make_iterable(Iterator begin, Iterator end) {return {begin, end};
}

第一段:列(Columns)

static inline iterable<MDColumn*> Columns(MDTable* tab)
{MDColumn* cols = tab->GetColumns();return make_iterable(cols, cols + tab->GetNumColumns());
}
  • tab->GetColumns() 返回 MDColumn* 指针(列数组的起始地址)
  • cols + tab->GetNumColumns() 表示末尾地址(即 [begin, end) 区间)
  • 把这个范围封装成 iterable<MDColumn*>,使得你可以这样用:
for (MDColumn* col : Columns(tab)) {// 使用 col
}

第二段:键(Keys)

static inline iterable<MDKey*> Keys(MDTable* tab)
{MDKey* keys = tab->GetKeys();return make_iterable(keys, keys + tab->GetNumKeys());
}

Columns 完全同理,用于遍历 MDKey* 指针数组。

总结:目的与好处

目标说明
简化遍历替代手动 for (int i=0; ...) 索引访问
更现代支持范围 for:for (auto x : Columns(tab))
更安全减少越界风险
更抽象将数组细节封装起来,提高可维护性
零开销所有逻辑在编译期展开,不引入额外开销

示例用法

void TransformTable(MDTable* tab) {for (MDColumn* col : Columns(tab)) {// 使用 col}for (MDKey* key : Keys(tab)) {// 使用 key}
}

Columns()Keys() 函数,利用了一个叫做 iterable 的包装器(定义在 "iterable.h" 里),把原本的裸指针数组和大小包装成一个支持范围 for 循环遍历的对象
具体来说:

  • Columns(tab) 返回一个从 tab->GetColumns() 指针开始,到 tab->GetColumns() + tab->GetNumColumns() 结束的区间包装对象。
  • Keys(tab) 返回一个从 tab->GetKeys() 指针开始,到 tab->GetKeys() + tab->GetNumKeys() 结束的区间包装对象。
    这让你可以写:
for (MDColumn* col : Columns(tab)) { ... }
for (MDKey* key : Keys(tab)) { ... }

代替以前麻烦且易错的索引循环,代码更简洁、易读,也减少越界风险。
简而言之:

  • Columns()Keys() 就是“把裸指针+长度”转换成“可迭代对象”,方便范围 for 循环。
  • make_iterable 返回的 iterable 类型封装了 begin/end 指针,支持标准的迭代器接口。
    这是 iterablemake_iterable 的完整模板实现,功能就是包装一对迭代器(这里用的是普通指针也可以)使它们可以用在范围 for 循环中。

逐步解释:

template<class It>
class iterable
{It m_first, m_last;  // 保存起始和结束的迭代器(或者指针)
public:iterable() = default;  // 默认构造函数iterable(It first, It last) : m_first(first), m_last(last) {}  // 构造时传入起始和结束It begin() const { return m_first; }  // begin() 返回起始迭代器It end() const { return m_last; }     // end() 返回结束迭代器
};
  • It 可以是任何支持迭代器语义的类型,比如指针、std::vector 迭代器、其他容器的迭代器。
  • begin()end() 是范围 for 循环所需的接口。
template<class It>
inline iterable<It> make_iterable(It a, It b)
{return iterable<It>(a, b);
}
  • make_iterable 是辅助函数,方便用两个迭代器快速创建 iterable 对象。
  • 这使得写法更简洁,比如:
auto r = make_iterable(ptr_start, ptr_end);
for (auto it : r) {// 使用 it
}

总结

  • iterable 是一个轻量级的范围封装器,提供标准 begin/end 接口。
  • make_iterable 用于创建 iterable,让范围循环写起来更方便。
  • 这就是你们之前 Columns()Keys() 函数背后的核心实现。

两个迭代器(begin 和 end)来封装一个“区间视图”或“容器视图”,这其实就是所谓的“range”或“iterator pair”概念。

核心点总结

  1. iterable
    封装两个迭代器(m_first, m_last),实现了 begin()end(),使其能在范围 for 循环中使用。
  2. “容器视图”(Container View)
    它不是一个真正拥有数据的容器,而是“内部反转”(Inside-out)容器。
    • 可以从已有对象或数组上动态生成。
    • 一个对象可以有多个“视图”或子区间。
    • 方便你只遍历一部分数据(子范围),而不用暴露数据结构内部实现。
  3. 类似概念的历史与名称
    • Marshall Clow 叫它 iterator_pair
    • Google 叫它 std::range(提案)
    • Boost 叫它 iterator_range
    • Alisdair Meredith 的提案(N2977)也称 std::range
      这些都本质上是两个迭代器组成的范围封装,目的是在 C++11 范围 for 还没支持真正的“ranges”概念之前,提供一种简单的范围抽象。
  4. C++ 标准与未来
    • C++17/20 标准后真正引入了 ranges 库(std::ranges),大大丰富了范围操作接口。
    • 这里你看到的实现是一个非常轻量级、简单的范围封装,是 ranges 思想的前身。

代码示例回顾

template<class It>
class iterable {It m_first, m_last;
public:iterable() = default;iterable(It first, It last) : m_first(first), m_last(last) {}It begin() const { return m_first; }It end() const { return m_last; }
};
template<class It>
inline iterable<It> make_iterable(It a, It b) {return iterable<It>(a, b);
}

作用

  • 让你快速把任意 [begin, end) 迭代器对包装成一个“容器”,
  • 然后用范围 for 来遍历它,
  • 不需要写额外的容器类,不需要暴露内部数据结构。

http://www.hkcw.cn/article/SubGorVpwM.shtml

相关文章

三大模块曝光:分钟级搭建专属平台,解锁算力灵活操控新体验,重新定义智能开发效率天花板

一. 蓝耘元生代 MaaS介绍及简单使用 背景介绍 创新产品定位&#xff1a; 蓝耘元生代 MaaS 平台于 2024 年 11 月 28 日推出&#xff0c;非传统智算平台&#xff0c;以资源聚合能力整合上下游资源&#xff0c;为用户提供优质全面服务。 核心模块功能&#xff1a; 集成智算算…

乾坤qiankun的使用

vue2 为主应用 react 为子应用 在项目中安装乾坤 yarn add qiankun # 或者 npm i qiankun -Svue主应用 在main.js中新增 &#xff08;需要注意的是路由模型为history模式&#xff09; registerMicroApps([{name: reactApp,entry: //localhost:3011,container: #container,/…

FreeRTOS实时操作系统学习笔记

一 RTOS入门 1.1 裸机与RTOS介绍&#xff08;了解&#xff09; 裸机编程是指在嵌入式系统中&#xff0c;直接在硬件上运行代码&#xff0c;没有操作系统的支持。这种方式下&#xff0c;开发者需要完全掌握硬件资源&#xff0c;包括时钟、中断、外设等。任务调度和资源管理都由…

MCP还是A2A?AI未来技术选型深度对比分析报告

引言 MCP&#xff08;Multi-Core Processor&#xff09;与A2A&#xff08;Asynchronous to Asynchronous&#xff09;分别代表了计算架构发展中的两种重要范式。前者延续传统冯诺依曼体系的并行优化路径&#xff0c;后者则试图突破同步时钟的物理限制。理解二者的本质差异&…

逐步检索增强推理的跨知识库路由学习

摘要 多模态检索增强生成&#xff08;MRAG&#xff09;在多模态大语言模型&#xff08;MLLM&#xff09;中通过在生成过程中结合外部知识来减轻幻觉的发生&#xff0c;已经显示出了良好的前景。现有的MRAG方法通常采用静态检索流水线&#xff0c;该流水线从多个知识库&#xff…

OpenRouter使用指南

OpenRouter 是一个专注于大模型&#xff08;LLM&#xff09;API 聚合和路由的服务平台&#xff0c;旨在帮助开发者便捷地访问多种主流大语言模型&#xff08;如 GPT-4、Claude、Llama 等&#xff09;&#xff0c;并提供统一的接口、成本优化和智能路由功能。以下是它的核心功能…

【Linux】权限chmod命令+Linux终端常用快捷键

目录 linux中权限表示形式 解析标识符 权限的数字序号 添加权限命令chmod 使用数字表示法设置权限 使用符号表示法设置权限 linux终端常用快捷键 &#x1f525;个人主页 &#x1f525; &#x1f608;所属专栏&#x1f608; 在 Linux 系统里&#xff0c;权限管理是保障系…

2018ToG | 可逆的灰度图像

写在前面&#xff1a;这篇论文是比较早期的论文了&#xff0c;但由于本人是第一次见到该方向的相关研究&#xff0c;所以觉得比较新奇。本文用以梳理这篇论文的阅读思路&#xff0c;文末附上了一些个人思考。 0. Abstract 一旦彩色图像被转换为灰度图像&#xff0c;普遍认为即…

Python打卡训练营Day43

DAY 43 复习日 作业&#xff1a; kaggle找到一个图像数据集&#xff0c;用cnn网络进行训练并且用grad-cam做可视化 数据集地址&#xff1a;Lung Nodule Malignancy 肺结核良恶性判断 进阶&#xff1a;并拆分成多个文件 import os import pandas as pd import numpy as np from…

mem0ai/mem0 v0.1.102版本全面升级,解锁多项前沿功能与文档优化!

大家好&#xff01;今天我们为大家带来mem0ai/mem0项目的重大版本更新——v0.1.102&#xff01;本次更新不仅带来了全新的功能扩展&#xff0c;更对项目的文档体系进行了深度优化&#xff0c;提升了整体用户体验和集成便捷性。无论你是mem0ai/mem0的忠实用户&#xff0c;还是刚…

导入典籍数据

1.从网上获取中医相关典籍数据&#xff0c;数目共600txt&#xff0c;总篇数14万 2.数据处理 获取到的数据结构大致如下 一个txt表示一本书&#xff0c;开头存有书籍相关的名字&#xff0c;作者&#xff0c;朝代&#xff0c;年份&#xff0c;之后每一个<目录>下都跟有一…

状态机实现文件单词统计

系统如何查找可执行文件 默认&#xff1a;在PATH路径下寻找文件文件下 执行当前目录下文件&#xff1a; ./&#xff1a;指定文件目录是当前目录 ./count:执行当前目录文件 编译.c文件为运行文件 gcc -o count 0voice.c #将0voice.c编译为名字count 为什么主函数要那么写&a…

[面试精选] 0021. 合并两个有序链表

文章目录 1. 题目链接2. 题目描述3. 题目示例4. 解题思路5. 题解代码6. 复杂度分析 1. 题目链接 21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; 2. 题目描述 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的…

【C++】内存管理

C/C内存分布 1.栈又叫堆栈–非静态局部变量/函数参数/返回值等等&#xff0c;栈是向下增长的。 2.内存映射段是高效的I/O映射方式&#xff0c;用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存&#xff0c;做进程间通信。 3.堆用于程序运行时动态内存分配&am…

基于javaweb的SpringBoot爱游旅行平台设计和实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…

ubuntu系统扩容

使用gparted工具扩容 安装方法&#xff1a; sudo apt-get update sudo apt-get install gparted 运行gparted&#xff0c;进行分区扩容 sudo gparted 打开软件后&#xff0c;先点击弹出窗口中的ok&#xff0c;若无弹出窗口&#xff0c;则无需点击。 点击右侧的下拉按钮 点…

计算机组成原理-计算机硬件的主要技术指标

机器字长 CPU一次能处理数据的位数&#xff0c;与CPU中的寄存器位数有关 运算速度 主频 核数&#xff0c;每个核支持的线程数 吉普森法&#xff1a;Tm ,根据指令集中每条指令的执行频率对指令集中所有指令的执行时间做加权平均。其中fi根据是否在程序运行时测量&#xff…

【TMS570LC4357】之相关驱动开发学习记录1

系列文章目录 【TMS570LC4357】之工程创建 【TMS570LC4357】之工程配置修改 【TMS570LC4357】之HALCOGEN使用 【TMS570LC4357】之相关问题及解决 ——————————————————— 前言 记录笔者在第一次使用TMS570过程中对外设驱动的一些学习碎片。 1. RTI 1.1 添…

多智能体在具身智能上的研究

在关于大模型的认知升级-CSDN博客中&#xff0c;我们提到大模型的终局是具身智能。那么&#xff0c;本文我们就来看看多智能体在具身智能上有了哪些应用。 Manus发布一天后迅速出现OpenManus、OWL等复刻项目。为什么可以这么快的复刻项目&#xff1f;其实&#xff0c;多智能体…

【HW系列】—安全设备介绍(开源蜜罐的安装以及使用指南)

文章目录 蜜罐1. 什么是蜜罐&#xff1f;2. 开源蜜罐搭建与使用3. HFish 开源蜜罐详解安装步骤使用指南关闭方法 总结 蜜罐 1. 什么是蜜罐&#xff1f; 蜜罐&#xff08;Honeypot&#xff09;是一种主动防御技术&#xff0c;通过模拟存在漏洞的系统或服务&#xff08;如数据库…