CppCon 2014 学习:Making C++ Code Beautiful
你说的完全正确,也很好地总结了 C++ 这门语言在社区中的两种典型看法:
C++ 的优点(Praise)
优点 | 含义 |
---|---|
Powerful | 允许底层控制、系统编程、高性能计算、模板元编程、并发等多种用途 |
Fast | 无运行时开销,接近汇编级别性能,适用于性能敏感场景 |
Flexible | 支持多范式(面向对象、泛型、函数式、过程式等)编程风格 |
Language for smart people | 经验丰富的程序员可以用它写出极其高效复杂的系统 |
C++ 的缺点(Criticism)
缺点 | 含义 |
---|---|
Ugly | 语法冗长、复杂(模板元编程特别难读) |
Complicated | 有太多特性,组合在一起容易出错(比如多重继承、SFINAE) |
Hard to read | 尤其是现代 C++ 中,涉及 decltype , std::enable_if , ranges 等 |
Language for smart people | 上手门槛高,初学者容易踩坑 |
两句话总结:
- C++ 是一把双刃剑:给你无比强大的能力,但你也必须承担复杂性的代价。
- C++ 是写得漂亮,但读得痛苦:经常“写的人是天才,读的人是受害者”。
这段代码虽然能在 C++ 中编译通过,但它“不是 C++ 风格”。我们来详细解释一下“为什么这段代码不符合现代 C++ 的风格”,也即为什么“这不是 C++”。
这段代码在语法上是合法的,但:
void f(char const* const file_name) {void* buffer1 = malloc(1024 * 1024);if (buffer1 != nullptr) {FILE* file = fopen(file_name, "r");if (file != nullptr) {// ...Read data into the buffer, or whatever...fclose(file);}free(buffer1);}
}
问题分析:这段代码是 C 风格,不是现代 C++ 风格
问题点 | 原因 |
---|---|
malloc/free | C++ 有更安全、类型安全的 new/delete 或更推荐的 std::vector 、std::unique_ptr 等自动管理资源的机制 |
FILE* , fopen , fclose | C++ 更推荐使用 std::ifstream / std::ofstream (RAII 风格)读取文件 |
手动资源管理 | 没有用智能指针或 RAII 来管理资源,容易忘记 free 或 fclose 导致内存/文件泄露 |
裸指针 void* | C++ 是类型安全语言,应尽量避免使用 void* ,除非在极特殊场景下(如与 C API 接口) |
C++ 风格改写版本(使用 RAII)
#include <fstream>
#include <vector>void f(const char* file_name) {std::vector<char> buffer(1024 * 1024); // 自动释放std::ifstream file(file_name, std::ios::binary);if (file) {file.read(buffer.data(), buffer.size());// 读取成功,file自动关闭}// buffer自动释放
}
优点:
- 使用
std::vector
管理内存,自动释放、无需手动free
。 - 使用
std::ifstream
,文件会在作用域结束时自动关闭(RAII:资源获取即初始化)。 - 避免了裸指针和类型不安全的
void*
。
理解本质
“This is not C++” 意思是:虽然可以在 C++ 编译器中运行,但没有发挥 C++ 的语言特性(如 RAII、模板库、类型安全等),更像是 “用 C 写的程序在 C++ 中运行”。
你这段总结准确传达了现代 C++ 编程的 基础准则。下面我们逐条解释这些建议的含义和原因,帮助你更深入地理解这些“从 C 向 C++ 编程习惯的转变”。
The Basics(基础建议)详解:
1. Compile at a high warning level
高级别编译警告设置(如
-Wall -Wextra -Werror
)
- 目的:让编译器帮助你提前发现潜在的 bug。
- 意义:写出更安全、更稳定、更规范的代码。
- 建议:在 GCC/Clang 中使用
-Wall -Wextra -Werror
;MSVC 中启用/W4
或/WAll
。
2. Stop writing C and try to port existing C code to C++
不要再用 C 的风格写 C++,要主动“现代化”旧 C 代码
- 替换
malloc/free
→new/delete
或更好 →std::vector
、std::unique_ptr
等 - 替换
FILE*
→std::ifstream
- 替换裸数组 →
std::array
或std::vector
- 关键思想是:利用 C++ 的类型系统和资源管理优势
3. Avoid #ifdef
s wherever possible
避免大量使用
#ifdef
,除非确实必要,且尽可能简洁
#ifdef
是编译时分支,会降低代码可读性和可维护性。- 替代方式:使用 策略模式、模板特化、标准库的
std::conditional
、多态等 C++ 技术手段。 - 如果真的要用,比如针对平台/编译器的宏判断,局部使用、保持最小范围。
4. Use RAII everywhere, even in the absence of exceptions
到处使用 RAII,即使你不开启异常处理
- RAII(Resource Acquisition Is Initialization)是一种通过对象生命周期管理资源的模式。
- 例子:
std::lock_guard
,std::unique_ptr
,std::ifstream
,std::scoped_thread
- 即使不开异常,也能确保作用域离开时资源自动释放,从而提升健壮性。
5. Keep functions linear and don’t write arrow code
函数保持“线性可读”,避免“箭头代码”(嵌套缩进)
// 箭头代码(过多嵌套)
if (x) {if (y) {if (z) {doSomething();}}
}// 线性写法(早退出,逻辑清晰)
if (!x) return;
if (!y) return;
if (!z) return;
doSomething();
6. Const-qualify everything (or, as much as possible )
把能加
const
的地方都加上(尽可能)
const
是 C++ 中的一种非常重要的表达意图的工具。- 对函数参数、返回值、成员函数、局部变量都应尽可能加
const
。 - 它能防止意外修改、增强代码文档性、改善编译器优化。
7. Don’t use C casts—eliminate as many casts as possible and use C++ casts where necessary
避免使用传统 C 风格的
(Type)
强制类型转换,必要时使用 C++ 的类型转换运算符:
| C++ 类型转换 | 用途 |
| ------------------- | --------------------- |
|static_cast<T>(x)
| 编译期转换,如基本类型、指针、enum |
|const_cast<T>(x)
| 添加或去除const
(仅当确实需要) |
|reinterpret_cast
| 危险的底层类型转换(尽量避免) |
|dynamic_cast<T>
| 用于有虚函数的多态类之间的安全运行时转换 |
- 这样写更可读、可查错、安全。
总结思维方式转变:
- 从“编译器不报错就行”转向“编译器是我的工具,尽可能帮我发现错误”
- 从“自己管理资源”转向“自动管理资源(RAII)”
- 从“C 风格结构+流程控制”转向“类型安全、结构化、线性逻辑”
有很多条应该是作者个人观点吧
这份 Agenda(议程)总结得非常精准又带点哲理感,尤其对代码风格和可维护性做了深刻反思。让我帮你拆解一下每条的含义和背后的设计哲学:
1. Macros are ugly
宏定义不好:
- 宏是预处理阶段替换文本,没有类型检查,容易出错且难调试。
- 宏扩展隐藏细节,降低代码可读性。
- 现代 C++ 推荐用
constexpr
,inline functions
,template
等替代宏。
2. Walls of code are ugly
长篇大论的代码让人反感:
- 大块没有分段或注释的代码,读起来像“墙”,让人望而生畏。
- 好的代码应短小精悍,结构清晰,分块明确,便于理解和维护。
3. Lambdas are beautiful
Lambda 表达式很美:
- 简洁地定义匿名函数,方便内联传递行为。
- 使代码更函数式、灵活,减少冗余和样板代码。
- 在 STL 算法和并发编程中特别好用。
4. Invisible code is beautiful
“看不见的代码”也美:
- 指抽象层很高,使用了封装和自动化的代码,比如模板库、智能指针、自动管理资源。
- 代码写得像自然语言,隐藏了复杂实现细节。
- 让调用者无需关注实现,只关心接口和语义。
5. Removing effort is beautiful
减少开发者负担是美的:
- 自动化管理资源(RAII)、智能指针、并发原语,让程序员不用手动管理内存、锁,减少出错。
- 简化接口和逻辑,避免重复劳动,提高效率。
6. Other people’s code is beautiful
欣赏别人的代码:
- 尊重和理解他人写的代码,能学习新技巧和思路。
- 提倡代码共享和合作,而非自我封闭。
- 认识到代码是团队产物,而非个人英雄主义。
7. Comments are ugly
评论是丑陋的:
- 当然不是说完全不写注释,而是代码本身应该尽可能清晰、表达力强,减少对注释的依赖。
- 坏注释(过时、错误、啰嗦)比没注释更糟糕。
- 好的代码是“自注释”的,即通过命名、结构、设计表达意图。
总结:
这份议程是在倡导现代、简洁、优雅、自动化、合作、和高可读性的代码文化。
特别提醒:
- 少用宏,多用现代 C++特性
- 避免代码臃肿
- 善用lambda和自动管理资源
- 让代码自己“讲故事”,减少注释负担
- 学习和尊重团队或社区里的其他代码
你这段内容很经典,总结得很准确,来帮你梳理和补充下理解:
Object-Like Macros(对象宏)
- 定义:通常是简单的常量替换,比如
#define MAX_SIZE 100
- 缺点:
- 无封装性,只是纯文本替换,没有作用域限制,容易冲突。
- 无类型安全,宏只是文本替换,不做类型检查,容易导致难查的错误。
- 替代方案:
- 在 C++ 中,优先用
enum
、constexpr
、static const
变量来代替常量宏。 - 但如果头文件要被 C 代码包含,有时候还得用宏(因为 C 不支持
constexpr
等)。
- 在 C++ 中,优先用
Function-Like Macros(函数宏)
- 定义:带参数的宏,比如
#define SQUARE(x) ((x)*(x))
- 缺点:
- 容易产生副作用:参数被多次计算,比如
SQUARE(i++)
会导致i++
被执行两次。 - 扩展行为不可预测,宏替换发生在预处理阶段,容易造成隐藏的错误。
- 不遵循函数的作用域和重载规则,难以调试。
- 容易产生副作用:参数被多次计算,比如
- 替代方案:
- 用普通函数代替,C++ 允许重载,且有类型检查。
- 如果函数模板可以解决泛型问题,推荐用模板函数。
- 如果想包裹表达式或代码块,可以用 lambda。
总结 Macros 的问题
- 宏不遵守作用域和名字查找规则,导致命名冲突和可维护性差。
- 宏是在编译前替换的,没有类型信息,容易出错。
- 宏的使用往往过度,很多时候有更好的替代方案。
你如果写 C++,建议尽量避免宏,用现代 C++ 的特性来替代。这样代码更安全、更可维护,也更容易阅读。
#define red 0
#define orange 1
#define yellow 2
#define green
#define blue3 4
#define purple 5
#define hot_pink 6
void f() { unsigned orange = 0xff9900; }
你这段代码:
#define red 0
#define orange 1
#define yellow 2
#define green 3
#define blue 4
#define purple 5
#define hot_pink 6void f() { unsigned orange = 0xff9900;
}
编译器报错的原因是宏替换导致的名字冲突。
详细解释:
- 你定义了宏
#define orange 1
,所以编译器预处理阶段会把代码中所有出现的orange
替换成1
。 - 这样函数中的代码变成:
void f() {unsigned 1 = 0xff9900;
}
- 这显然不是合法的变量定义,导致编译器报错:
left operand must be l-value
是说1
不是变量名,不能作为左值赋值。syntax error : missing ';' before 'constant'
是语法错误。warning C4091
是因为语法没被正确解析。
解决方法:
不要让宏名和变量名冲突。
例如,改变量名:
void f() { unsigned orange_color = 0xff9900;
}
或者:
void f() { unsigned my_orange = 0xff9900;
}
这样 orange_color
或 my_orange
不会被宏替换,代码也能正确编译。
另外的好做法:
用 enum
或 constexpr
替代宏,避免这种名字冲突问题:
enum Color {red,orange,yellow,green,blue,purple,hot_pink
};void f() {unsigned orange = 0xff9900; // 这里 orange 是变量,不会被替换
}
总结:
宏会直接文本替换代码,变量名千万别和宏名相同,不然就会出现语法错误。你只要换个变量名就行了。
你这段代码:
#define RED 0
#define ORANGE 1
#define YELLOW 2
#define GREEN 3
#define BLUE 4
#define PURPLE 5
#define HOT_PINK 6void g(int color) {}void f() {g(HOT_PINK); // Ok, expands to g(6)g(9000); // Also valid syntactically, but logically “not ok”
}int main() {}
重点:
- 你用宏定义了一组颜色常量,调用
g(HOT_PINK)
其实是g(6)
,这是合法的。 - 你也可以调用
g(9000)
,语法没问题,编译器也不会报错,因为g
参数是int
,任何整数都合法。 - 但是从逻辑上来说,
9000
并不是你定义的颜色值之一,调用g(9000)
是“不合法的”用法,但编译器无法检测出来。
这反映了宏的一个缺点:
- 宏只是文本替换,没有类型信息,也没有范围限制。
- 不能告诉编译器:
color
参数必须是宏定义的枚举值之一。
解决方案:
用 enum
或 enum class
来替代宏:
enum Color {RED,ORANGE,YELLOW,GREEN,BLUE,PURPLE,HOT_PINK
};void g(Color color) {}void f() {g(HOT_PINK); // Ok// g(static_cast<Color>(9000)); // 语法允许,但逻辑上有警告或明确要强制转换
}
- 这样
g
参数限定为Color
类型,给编译器更多类型信息,有助于减少误用。 - 使用
enum class
会更安全,避免隐式转换。
总结:
- 宏定义的常量没有类型信息,容易导致传入非法值,但编译器没法警告。
- 使用
enum
或enum class
能让类型安全和语义更清晰,减少错误。
error C2664: ‘void g(color_type)’ : cannot convert argument 1 from ‘int’ to ‘color_type’
你这段代码的关键问题是:
enum color_type
{red, orange, yellow, green, blue, purple, hot_pink
};enum traffic_light_state
{red, yellow, green
};
两个 enum
都定义了相同名字的枚举成员,比如 red
, yellow
, green
。这会导致 枚举成员名冲突,编译器报错:
error C2365: 'red' : redefinition; previous definition was 'enumerator'
error C2365: 'yellow' : redefinition; previous definition was 'enumerator'
error C2365: 'green' : redefinition; previous definition was 'enumerator'
原因
普通的 enum
里,枚举成员名在定义它的作用域中是直接可见的,也就是说它们会污染全局命名空间,造成名字冲突。
解决方案
1. 使用 enum class
(强类型枚举)
enum class color_type
{red, orange, yellow, green, blue, purple, hot_pink
};enum class traffic_light_state
{red, yellow, green
};
void f()
{int x = static_cast<int>(color_type::red); int y = static_cast<int>(traffic_light_state::red);
}
enum class
的枚举成员被限定在自己的作用域内,需要加作用域名访问(例如color_type::red
)。- 不会污染全局命名空间,避免名字冲突。
- 更安全,防止隐式转换。
2. 给普通 enum
成员不同名字
比如:
enum color_type
{color_red, color_orange, color_yellow, color_green, color_blue, color_purple, color_hot_pink
};
enum traffic_light_state
{light_red, light_yellow, light_green
};
- 这种做法简单粗暴,但是代码可读性稍差。
小结
- 你遇到的名字冲突是由于普通
enum
的枚举成员在定义范围中直接暴露导致的。 - 推荐用
enum class
解决名字污染问题,也更现代和安全。
这段代码的意思是:
enum : uint32_t
{max_uint32_t = static_cast<uint32_t>(-1)
};
解释
- 这是无名枚举(anonymous enum),定义了一个匿名的枚举类型,底层类型为
uint32_t
。 - 枚举中只有一个枚举值
max_uint32_t
。 max_uint32_t
的值被设成static_cast<uint32_t>(-1)
,也就是 无符号32位整数的最大值,即0xFFFFFFFF
(4294967295)。- 这样做是定义一个类型安全的常量,用枚举的方式来替代宏定义。
为什么这么写?
enum
里的值默认是整型常量表达式,方便编译时使用。- 指定了底层类型为
uint32_t
,确保枚举成员的类型是32位无符号整型。 static_cast<uint32_t>(-1)
把-1
转换成无符号数后就是最大值。- 无名枚举不产生新的类型名,只定义枚举常量。
总结
这段代码定义了一个无符号32位整数最大值的常量 max_uint32_t
,写法比宏更安全且更符合C++风格。
总结就是:
- 尽量用枚举值(enumerators)来定义命名常量,特别是带作用域的枚举(
enum class
),这样可以避免名字冲突和类型安全问题。 - 也可以用
static const
变量来定义常量,但枚举通常更简洁且效率高(编译时常量)。 - 如果需要和C代码兼容,或者必须使用预处理器(
#define
),那么宏可能还会被用到,但尽量避免。
举个例子:
enum class Color : int {Red,Green,Blue
};
static const int MaxSize = 100;
这样写既安全又清晰。
如果要兼容C:
#define MAX_SIZE 100
如果你想,我可以帮你演示如何用现代C++方式定义命名常量!
总结一下这里的问题和改进:
宏版的 make_char_lowercase(c)
#define make_char_lowercase(c) \((c) = (((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))
- 问题:
- 多次对参数
c
求值,可能导致副作用和错误。 - 不安全,难以调试,阅读也不方便。
- 在调用如
make_string_lowercase
时,宏展开导致代码变得混乱且难以理解。
- 多次对参数
改进:用函数替代宏
inline char make_char_lowercase(char& c) {if (c >= 'A' && c <= 'Z') {c = c - 'A' + 'a';}return c;
}
- 优势:
- 函数参数只求值一次,没有副作用。
- 类型安全,易于调试和维护。
- 语义明确,易读。
- 内联函数在性能上与宏相当。
总结
- 避免用宏来实现函数功能,特别是操作参数的宏。
- 用
inline
函数替代宏,实现类型安全且清晰的代码。 - 这符合现代C++的最佳实践。
为什么用函数替换函数式宏?
- 可读性更高:函数结构清晰,易于理解。
- 维护更简单:函数可以写多行代码,逻辑清晰,不受宏表达式限制。
- 调试更方便:调试器能单步进入函数,而宏展开后代码难以跟踪。
- 行为符合预期:参数只计算一次,避免宏展开带来的多次求值副作用。
- 性能几乎无差别:
inline
函数在现代编译器中性能和宏一样快。
这是现代C++编程的最佳实践。你可以放心用函数替代大部分宏,尤其是那些做“函数样”的宏。
代码太长太乱(Wall of Code)
- 代码太长太乱(Wall of Code),看起来非常难维护,理解也很吃力。
- 拆分成函数不仅能理清思路,还让每块代码有明确职责,函数名和参数名也能起到“文档”的作用。
- 参数传递规则的变化让拆分更灵活,也避免了一开始就做过度优化,导致代码复杂难懂。
- 如果类型让代码变复杂,考虑用更合适的类型,比如用
std::string
代替char*
,或者把多相关变量封装成类。 - 多用标准库算法,能少写代码,提高代码质量,还省去写测试和调优的麻烦。
lamdba
单看 [](){}
这小小的一对括号,确实不怎么“美丽”——特别是没什么内容的时候,看着像个空壳。
但这就是C++里lambda的“种子”形态,后面可以捕获变量、带参数、写返回值、写复杂逻辑,瞬间变成一个强大的小匿名函数。
比如:
[]() { std::cout << "Hello, Lambda!\n"; }
就很直接地把函数写在了代码里,省了单独定义函数的麻烦。
所以虽然“长相”不咋地,但它们能让代码变得更灵活、简洁,简直是C++现代编程的利器。
理解!这段代码展示了一个非常现代的 C++ lambda 用法,利用了模板参数(auto
)、完美转发(std::forward
)和捕获([=]
)。
auto vglambda = [](auto printer) {return [=](auto&&... ts) {printer(std::forward<decltype(ts)>(ts)...);return [=]() { printer(ts...); };};
};
vglambda
是一个接受函数参数printer
的 lambda。- 它返回另一个 lambda,这个 lambda 是一个泛型(接受任意参数包
ts...
)。 - 在这个内部 lambda 里,
printer
会被调用一次,传入参数包ts...
(用完美转发传递,保持值类别)。 - 它再返回一个无参的 lambda,这个 lambda 捕获了参数
ts...
,当调用时,再次调用printer(ts...)
。
也就是说,vglambda
产生了一个「两步调用」的结构:
- 先用参数调用一次
printer
。 - 然后返回一个无参数的函数,可以稍后再调用,重复打印这些参数。
用法示例:
auto printer = [](auto&&... args) {(std::cout << ... << args) << '\n'; // 折叠表达式打印所有参数
};
auto f = vglambda(printer);
auto delayed = f(1, 2, 3); // 立即打印 "123"
delayed(); // 稍后再次打印 "123"
这个模式有点像把打印操作包装成可立即执行和延迟执行两种方式,挺有趣的!
std::vector<int> const v = {1, 2, 3, 4, 5};
std::for_each(begin(v), end(v), [](int const n) { std::cout << n << '\n'; });
这段代码使用了 C++ 标准库中的 std::for_each
算法,结合了 lambda 表达式,做的是对 std::vector<int>
中的每个元素执行一个操作。
逐步解释:
std::vector<int> const v = {1, 2, 3, 4, 5};
创建了一个不可变的整数向量,包含 1 到 5。std::for_each(begin(v), end(v), [](int const n) { std::cout << n << '\n'; });
从向量的开始到结束,对每个元素调用 lambda 函数。
这个 lambda 通过值捕获参数n
(不可变的int
),然后打印到标准输出,后面跟一个换行符。
简而言之,这会依次打印出:
1
2
3
4
5
这是一种很简洁、优雅的写法,用来遍历容器并对每个元素做操作。
std::thread t([] { std::cout << "Hello, CppCon\n"; });
这行代码是用 C++11 的线程库创建并启动一个新线程:
std::thread t(...)
— 创建一个新线程对象t
。- 传入的参数是一个无参 lambda
[]{ std::cout << "Hello, CppCon\n"; }
,线程启动后执行这个函数体。 - 线程会异步打印字符串
"Hello, CppCon\n"
到控制台。
执行完这行后,主线程和新线程会并行运行,新线程会在后台打印这句话。
需要注意的是,通常你还需要调用t.join()
来等待线程结束,防止主线程提前退出导致程序异常结束:
std::thread t([]{ std::cout << "Hello, CppCon\n"; });
t.join();
这样可以保证输出一定会执行完。
CreateThread(nullptr, 0,[](void*) {std::cout << "Hello, CppCon people\n";return 0ul;},nullptr, 0, nullptr);
这段代码是 Windows API 的 CreateThread
调用,用来创建一个新的线程:
- 第一个参数
nullptr
:默认安全属性(线程句柄不可继承)。 - 第二个参数
0
:默认堆栈大小。 - 第三个参数是线程函数,这里用了一个 C++ lambda:
它接受一个[](void*) { std::cout << "Hello, CppCon people\n"; return 0ul; }
void*
参数,打印一行信息,然后返回0ul
(线程退出码)。 - 第四个参数
nullptr
:传递给线程函数的参数(这里没有传)。 - 第五个参数
0
:默认启动线程状态(立即运行)。 - 第六个参数
nullptr
:不需要线程ID。
不过注意,CreateThread
需要线程函数是LPTHREAD_START_ROUTINE
,也就是:
DWORD WINAPI ThreadFunc(LPVOID lpParam);
你的 lambda符合这个签名吗?
实际上,lambda会自动转换成函数指针(如果不捕获变量),但捕获变量的lambda不能转换成普通函数指针。
如果这段代码能通过编译,说明用的是无捕获lambda,转换成了合适的函数指针。
总的来说,这是一种用 C++ lambda 代替传统线程函数指针的技巧,用于 Windows 线程API。
extern "C" errno_t my_amazing_c_function() {return translate_exceptions([&] {// ... code that may throw ...});
}
这段代码展示了如何用 extern "C"
修饰符暴露一个符合 C 语言调用约定的函数,同时在函数体内用 C++ 风格的异常处理包装代码。
解释一下:
extern "C"
告诉编译器这个函数用 C 的链接方式导出,避免 C++ 名字改编(name mangling),方便被 C 代码或其他语言调用。errno_t my_amazing_c_function()
返回类型是errno_t
,通常是错误码类型(Windows 上常见)。translate_exceptions([&]{ ... })
这是一个函数调用,接受一个 lambda,lambda 里是可能会抛异常的 C++ 代码。translate_exceptions
会捕获所有异常并把它们转换成一个错误码(errno_t),这样外部 C 代码就不会看到异常,只能看到错误码。
总结:- 你用 C++写实现细节,允许抛异常。
- 用
translate_exceptions
把异常捕获并转成 C 风格的错误码。 - 用
extern "C"
让函数接口兼容 C 语言调用。
很实用的“混用 C 和 C++”技巧!
如果你需要,我可以帮你写一个translate_exceptions
的示范实现。
database const* target_scope(nullptr);
switch (resolution_scope.table()) {case table_id::module:target_scope = &module.database();break;case table_id::module_ref:target_scope = &resolve_module_ref(resolution_scope.as<module_ref_token>());break;case table_id::assembly_ref:target_scope = is_windows_runtime_assembly_ref(assembly_ref_scope)? &resolve_namespace(usable_namespace): &resolve_assembly_ref(assembly_ref_scope);break;default:assert_unreachable();
}
这段代码是一个典型的 switch
结构,用来根据 resolution_scope.table()
返回的不同 table_id
值,确定并设置指向某个数据库对象的指针 target_scope
。
具体分析:
-
database const* target_scope(nullptr);
定义一个指向database
类型的常量指针,初始值为nullptr
。 -
switch (resolution_scope.table())
根据resolution_scope
对象的table()
方法的返回值,进入不同的分支。 -
case table_id::module:
如果table()
返回module
,则把target_scope
指向module.database()
。 -
case table_id::module_ref:
如果是module_ref
,通过resolve_module_ref
函数解析,并指向结果。 -
case table_id::assembly_ref:
如果是assembly_ref
,根据is_windows_runtime_assembly_ref
判断条件选择调用resolve_namespace
或resolve_assembly_ref
,然后赋值。 -
default:
断言不可达代码assert_unreachable()
,表示如果遇到未知的table_id
,程序逻辑有错误。
总结:
这段代码根据resolution_scope
的类型,选择合适的数据库范围target_scope
来操作。写法清晰,使用了枚举类型的switch-case
,同时用断言确保不会处理无效情况。
database const& target_scope([&]() -> database const& {switch (resolution_scope.table()) {case table_id::module:return module.database();case table_id::module_ref:return resolve_module_ref(resolution_scope.as<module_ref_token>());case table_id::assembly_ref:return is_windows_runtime_assembly_ref(assembly_ref_scope)? resolve_namespace(usable_namespace): resolve_assembly_ref(assembly_ref_scope);default:assert_unreachable();}
}());
这段代码是把之前用指针赋值的写法改成了用立即调用的 lambda 表达式来初始化一个 database const&
类型的引用变量 target_scope
。
具体分析:
database const& target_scope([&]() -> database const& {switch (resolution_scope.table()) {case table_id::module:return module.database();case table_id::module_ref:return resolve_module_ref(resolution_scope.as<module_ref_token>());case table_id::assembly_ref:return is_windows_runtime_assembly_ref(assembly_ref_scope)? resolve_namespace(usable_namespace): resolve_assembly_ref(assembly_ref_scope);default:assert_unreachable();}
}());
database const& target_scope(...)
是定义一个对常量database
的引用。[...]() -> database const& { ... }()
是一个lambda 表达式,并且立即调用了它。[...]
这里用的是捕获[&]
,即捕获外部作用域中的所有变量的引用。-> database const&
明确了 lambda 返回的类型是database
类型的常量引用。
- lambda 内部通过
switch
判断resolution_scope.table()
并返回相应的database
引用。 assert_unreachable()
确保不可能到达的默认分支。
为什么这么写?
- 表达式初始化:用一个表达式直接初始化
target_scope
,代码紧凑且清晰。 - 避免指针:直接返回引用,避免了使用指针(
database const*
),更安全。 - 保持局部变量不可变:使用
const&
防止意外修改。 - 作用域局部性好:
target_scope
在此作用域里只读且有效,符合 RAII 风格。
这句 “Invisible code is beautiful”(“隐形代码是美的”)是在强调一个编程理念:代码越简洁、职责越明确,越容易维护和阅读。
“Invisible code” 是什么意思?
在这个语境下,它指的是:
- 你不需要显式地写出来的代码行为
- 比如:构造函数自动析构、智能指针自动释放资源、RAII 自动管理生命周期、范围退出自动清理(如
std::lock_guard
)等
举例说明
当程序执行到 }
,也就是一个作用域结束时,会发生很多 自动行为,比如:
void example() {std::lock_guard<std::mutex> lock(m); // 加锁std::vector<int> v = {1, 2, 3};// ...
} // 到这里,lock 自动释放(mutex unlock),v 自动析构(释放内存)
这段代码在结束 }
的时候:
lock_guard
自动释放锁vector
自动析构释放内存
你看不到释放操作,但它确实发生了。这就是“隐形代码”的魔力 —— 它减少了你手动管理资源的负担,提升代码的简洁性和可靠性。
总结
“Invisible code” 指的是程序自动帮你做的事。
好处包括:
- 减少错误(如忘记释放内存/锁)
- 更少样板代码(如
free()
、delete
、unlock()
) - 更清晰的控制流和更少意外副作用
现代 C++ 编程风格(比如 RAII、封装、自动资源管理) 带来的一些“意想不到的好处”:
Unexpected Benefits(意外好处)
Consistent cleanup(一致性的清理)
- 使用 RAII(Resource Acquisition Is Initialization)或智能指针(如
std::unique_ptr
)后,资源在作用域结束时自动释放; - 不再需要手动调用
delete
、free
、CloseHandle
等函数; - 无论正常退出还是异常退出,资源都能正确清理。
意义:更少的资源泄漏、更少的 bug、更可靠的程序
Encapsulation(封装)
- 清理代码、状态控制、异常处理逻辑可以封装到类或函数中;
- 减少重复代码,提高复用性;
- 更容易测试和维护。
意义:模块化设计,清晰职责边界
Important code becomes visible(重要代码更清晰)
- 重点业务逻辑(如连接数据库、发送数据等)不再被大量的异常处理、资源清理代码“淹没”;
- 更容易读懂函数真正的意图;
- 控制流更清晰,没有
goto
、“墙”式代码(wall of code)问题。
意义:代码更易读、更易审查、更易协作
总结一句话
现代 C++ 风格(RAII、封装、消除 goto
)不仅让代码更安全和稳定,还让代码更整洁、更具表达力。
这一页的核心是强调 “移除编码负担,让代码更清晰、更安全” 的美学理念。
Removing Effort is Beautiful(移除多余的努力是美的)
代码难写 = 难读 = 难维护
- 写得费劲的代码,别人看得也费劲;
- 难以维护、调试、测试,未来改动成本高。
表达清晰的代码更容易理解
- 清晰表达“意图”的代码对你和他人都更友好;
- 少用技巧,多用结构。
防止犯错的代码更稳健
- 使用现代 C++ 特性,可以避免很多低级错误:
- 范围-based for:避免越界、错写索引;
for (auto& item : my_vector) { ... }
- 标准算法:更简洁、更安全(如
std::find_if
,std::copy_if
等);std::sort(vec.begin(), vec.end());
auto
(有节制地使用):减少重复、避免类型错误;auto it = my_map.find(key); // 自动类型推导,避免写冗长的类型名
- 范围-based for:避免越界、错写索引;
总结
移除“额外努力”的做法,不仅提高开发效率,更关键的是让代码:
- 更可读
- 更易维护
- 更不容易出错
这正是现代 C++ 设计哲学的精髓:表达意图、减少错误、拥抱简洁。
相关文章:

CppCon 2014 学习:Making C++ Code Beautiful
你说的完全正确,也很好地总结了 C 这门语言在社区中的两种典型看法: C 的优点(Praise) 优点含义Powerful允许底层控制、系统编程、高性能计算、模板元编程、并发等多种用途Fast无运行时开销,接近汇编级别性能&#x…...
副本(Replica)在Elasticsearch中扮演什么角色?
在Elasticsearch(ES)中,副本(Replica)是主分片(Primary Shard)的镜像拷贝,与主分片共同构成分布式索引的高可用性和高性能架构。副本的设计目标是解决数据冗余、负载均衡和故障恢复等核心问题,其具体作用和原理如下: 一、副本的核心角色与功能 1. 数据冗余与故障恢…...

据传苹果将在WWDC上发布iOS 26 而不是iOS 19
苹果可能会对其操作系统的编号方式做出重大改变,基于年份的新版系统会将iOS 19重新命名为 iOS 26,同时 macOS 也会以同样的方式命名。 苹果的编号系统相当简单,版本号每年都会像钟表一样定期更新。然而,今年秋天情况可能有所不同&…...
整理了Windows(7—11)官方镜像下载链接和各版本区别介绍
原文《整理了Windows(7—11)官方镜像下载链接和各版本区别介绍》 引言 在安装或重装Windows系统时,使用微软官网提供的正版ISO镜像可以保证系统完整性和安全更新,避免使用第三方盗版镜像带来的恶意软件、广告风险。 本期汇总了微…...
数据库主键与索引详解
目录 主键核心特性 主键必要性问题 索引核心解析 基本定义 优劣分析 索引类型对比 数据结构实现 工作原理 主键与索引核心区别 主键核心特性 唯一标识:确保字段数据不重复且不为空数量限制:每表仅允许存在一个主键存储类型:通常采用…...
RTOS:启动调度器的作用(含源码逐行解读)
文章目录 前言一、启动调度器二、详细逻辑分析三、逐行分析3.1、traceENTER_vTaskStartScheduler3.2、configASSERT( ( sizeof( UBaseType_t ) * taskBITS_PER_BYTE ) > configNUMBER_OF_CORES );3.3、xReturn prvCreateIdleTasks();3.4、xTimerCreateTimerTask();3.5、fre…...
【Python 进阶】抽象基类(Abstract Base Class)
abc 模块和抽象基类(Abstract Base Class)的概念: 本文的示例代码来源于 break-a-scene. abc 是 Python 标准库中的一个模块,全称是 “Abstract Base Classes”(抽象基类)。它提供了一种定义抽象类和抽象方法的机制。在以下代码…...
Armv7l或树莓派32位RPI 4B编译faiss
pip3 install faiss-cpu当然找不到预编译的包 手动下载 git clone https://github.com/facebookresearch/faiss.git cd faiss #能需要切换到特定版本标签,例如 v1.7.1,这个版本Cmake 3.18可以过,因为apt install安装的cmake只更新到这里&am…...

嵌入式开发STM32 -- 江协科技笔记
1.背景介绍及基础认知 8大输入输出 斯密特触发器:高于设定阈值输出高电平,低于设定阈值输出低电平 有关上拉输入、下拉输入、推挽输出、开漏输出、复用开漏输出、复用推挽输出以及浮空输入、模拟输入的区别 1、上拉输入:上拉就是把电位拉高…...

[网页五子棋][用户模块]客户端开发(登录功能和注册功能)
文章目录 客户端开发登录功能htmlcsscommon.csslogin.css jQuery引入 jquery 运行程序注册功能 客户端开发 登录功能 html <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport&…...
nt!MiInitializeSystemCache函数分析之PointerPte->u.List.NextEntry的由来
第一部分: 1: kd> dd 0xc0304200 c0304200 c10c0000 00000000 00000000 00000000 c0304210 00000000 00000000 00000000 00000000 c0304220 00000000 00000000 00000000 00000000 c0304230 00000000 00000000 00000000 00000000 c0304240 00000000 00000000…...

MQTT协议,EMQX部署,MQTTX安装学习
一、MQTT概述 1.什么是MQTT MQTT是一种基于“发布订阅“”模式的消息传输协议。 消息:设备和设备之间传输的数据,或者服务和服务之间要传输的数据。 协议:传输数据时所遵循的规范。 2.常见的通讯模式 (1)客户端-服…...
如何理解UDP 和 TCP 区别 应用场景
UDP与TCP的定义、特性、使用场景及对比表格 定义 UDP(User Datagram Protocol)是一种无连接的传输层协议,提供不可靠的数据报服务。 TCP(Transmission Control Protocol)是一种面向连接的传输层协议,提供…...

60天python训练计划----day40
DAY 40 训练和测试的规范写法 知识点回顾: 彩色和灰度图片测试和训练的规范写法:封装在函数中展平操作:除第一个维度batchsize外全部展平dropout操作:训练阶段随机丢弃神经元,测试阶段eval模式关闭dropout 一.单通道图…...

干泵,干式螺杆真空泵
干式真空泵: 无油干式机械真空泵(又简称干式机械泵)是指泵能从大气压力下开始抽气,又能将被抽气体直接排到大气中去,泵腔内无油或其他工作介质,而且泵的极限压力与油封式真空泵同等量级或者接近的机械真空泵…...

Tailwind CSS 实战:基于 Kooboo 构建 AI 对话框页面(五):语音合成输出与交互增强
Tailwind CSS 实战,基于Kooboo构建AI对话框页面(一) Tailwind CSS 实战,基于Kooboo构建AI对话框页面(二):实现交互功能 Tailwind CSS 实战,基于 Kooboo 构建 AI 对话框页面&#x…...

职业本科院校无人机专业人才培养解决方案
2023年的中央经济工作会议强调了以科技创新推动现代化产业体系构建的重要性,并提出发展生物制造、商业航天、低空经济等战略性新兴产业。低空经济,依托民用无人机等低空飞行器,在多场景低空飞行活动的牵引下,正逐步形成一个辐射广…...
利用机器学习优化数据中心能效
数据中心作为现代社会的数字基础设施,支撑着云计算、大数据分析、人工智能等关键技术的发展。然而,随着数据中心规模的不断扩大,其能源消耗问题也日益凸显。如何提高数据中心的能源效率,降低运营成本,同时减少环境影响…...

软件评测机构如何保障质量?检测资质、技术实力缺一不可
软件评测机构在保障软件质量上起着关键作用,对软件行业的健康发展极为关键。它们采用专业的技术手段和严格的评估流程,对软件的运行效果、功能等多方面进行细致的审查,为开发者和使用者提供了客观、公正的参考依据。 检测资质正规软件评测机…...
微软开源bitnet b1.58大模型,应用效果测评(问答、知识、数学、逻辑、分析)
微软开源bitnet b1.58大模型,应用效果测评(问答、知识、数学、逻辑、分析) 目 录 1. 前言... 2 2. 应用部署... 2 3. 应用效果... 3 1.1 问答方面... 3 1.2 知识方面... 4 1.3 数字运算... 6 1.4 逻辑方面... …...
ubuntu 安装上传的 ffmpeg_7.1.1.orig.tar.xz并使用
在 Ubuntu 系统上离线安装 make 需要提前准备好所有依赖包。以下是详细的操作步骤: 准备工作:在有网络的机器上下载所需软件包 查找依赖关系 在有网络的 Ubuntu 机器上(需与目标机器相同版本)执行: # 获取 make 及其依…...
Web3怎么开发类似MetaMask的钱包
开发一个类似MetaMask的钱包,关键就是要利用以太坊提供的官方接口和标准,主要涉及以下几点: 1. 你要用到的以太坊官方接口和规范 JSON-RPC API 以太坊节点(如Geth、OpenEthereum等)通过JSON-RPC接口暴露各种功能&…...

Linux多线程(六)之线程控制4【线程ID及进程地址空间布局】
文章目录 线程ID及进程地址空间布局线程局部存储 线程ID及进程地址空间布局 pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。 该线程ID和前面说的线程ID不是一回事。 前面讲的线程ID属于进程调度的范畴。 因为线程是轻量级进程ÿ…...

1.什么是node.js、npm、vue
一、Node.js 是什么? 😺 定义: Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,让你可以在浏览器之外运行 JavaScript 代码,主要用于服务端开发。 😺从计算机底层说:什么是“运…...

Xamarin入门笔记(Xamarin已经被MAUI取代)
初级代码游戏的专栏介绍与文章目录-CSDN博客 Xamarin入门 概述 环境 Android开发环境比较简单,自带模拟器,实体机打开开发者模式即可。 iOS开发环境比较复杂,必须搭配Mac电脑,Windows连接Mac开发可能有问题(比如发…...

排查Oracle文件打开数过多
Oracle数据库在运行过程中,会打开大量的文件以执行其操作,包括数据文件、控制文件、日志文件等。如果Oracle用户打开的文件数过多,可能会引起系统性能下降。下面将深入分析Oracle用户文件打开数的优化策略,以帮助数据库管理员&…...

应用层协议http(无代码版)
目录 认识URL urlencode 和 urldecode HTTP 协议请求与响应格式 HTTP 的请求方法 GET 方法 POST 方法 HTTP 的状态码 HTTP 常见 Header Location 关于 connection 报头 HTTP版本 远程连接服务器工具 setsockopt 我们来学习应用层协议http。 虽然我们说, 应用层协…...

8.5 Q1|广州医科大学CHARLS发文 甘油三酯葡萄糖指数累积变化与 0-3期心血管-肾脏-代谢综合征人群中风发生率的相关性
1.第一段-文章基本信息 文章题目:Association between cumulative changes of the triglyceride glucose index and incidence of stroke in a population with cardiovascular-kidney-metabolic syndrome stage 0-3: a nationwide prospective cohort study 中文标…...
交叉编译tcpdump工具
1.导出交叉编译工具链 export PATH$PATH:/opt/rockchip/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf/bin 下载源码包libpcap-1.10.5,配置、并编译安装。 github仓库地址 ./configure --hostarm-linux CCarm-linux-gnueabihf-gcc --prefix$PWD/install …...
【Python-Day 20】揭秘Python变量作用域:LEGB规则与global/nonlocal关键字详解
Langchain系列文章目录 01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...