当前位置: 首页 > news >正文

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/freeC++ 有更安全、类型安全的 new/delete 或更推荐的 std::vectorstd::unique_ptr 等自动管理资源的机制
FILE*, fopen, fcloseC++ 更推荐使用 std::ifstream / std::ofstream(RAII 风格)读取文件
手动资源管理没有用智能指针或 RAII 来管理资源,容易忘记 freefclose 导致内存/文件泄露
裸指针 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/freenew/delete 或更好 → std::vectorstd::unique_ptr
  • 替换 FILE*std::ifstream
  • 替换裸数组 → std::arraystd::vector
  • 关键思想是:利用 C++ 的类型系统和资源管理优势

3. Avoid #ifdefs 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++ 中,优先用 enumconstexprstatic const 变量来代替常量宏。
    • 但如果头文件要被 C 代码包含,有时候还得用宏(因为 C 不支持 constexpr 等)。

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_colormy_orange 不会被宏替换,代码也能正确编译。

另外的好做法:

enumconstexpr 替代宏,避免这种名字冲突问题:

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参数必须是宏定义的枚举值之一

解决方案:

enumenum 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 会更安全,避免隐式转换。

总结:

  • 宏定义的常量没有类型信息,容易导致传入非法值,但编译器没法警告。
  • 使用 enumenum 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 产生了一个「两步调用」的结构:

  1. 先用参数调用一次 printer
  2. 然后返回一个无参数的函数,可以稍后再调用,重复打印这些参数。

用法示例:

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_namespaceresolve_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()deleteunlock()
  • 更清晰的控制流和更少意外副作用

现代 C++ 编程风格(比如 RAII、封装、自动资源管理) 带来的一些“意想不到的好处”:

Unexpected Benefits(意外好处)

Consistent cleanup(一致性的清理)
  • 使用 RAII(Resource Acquisition Is Initialization)或智能指针(如 std::unique_ptr)后,资源在作用域结束时自动释放;
  • 不再需要手动调用 deletefreeCloseHandle 等函数;
  • 无论正常退出还是异常退出,资源都能正确清理。

意义:更少的资源泄漏、更少的 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); // 自动类型推导,避免写冗长的类型名
      

总结

移除“额外努力”的做法,不仅提高开发效率,更关键的是让代码:

  • 更可读
  • 更易维护
  • 更不容易出错
    这正是现代 C++ 设计哲学的精髓:表达意图、减少错误、拥抱简洁

相关文章:

  • Linux中的shell脚本
  • 【Dockerfile 完全参数化的通用 APT 源配置方案】
  • 【Python】yield from 功能解析
  • 模块化设计,static和extern(面试题常见)
  • 互联网大厂Java求职面试:云原生微服务架构设计与AI大模型集成实战
  • C# SolidWorks二次开发-实战1,找文件名不同实体相同的零件。
  • Jenkins 2.479.1安装和邮箱配置教程
  • 机器学习算法03:聚类算法
  • 鸿蒙 HarmonyOS - SideBarContainer 组件自学指南
  • Redis 插入中文乱码键
  • AR-HUD 光波导方案优化难题待解?OAS 光学软件来破局
  • 直播预告 | 聚焦芯必达|打造可靠高效的国产 MCU 与智能 SBC 汽车解决方案
  • 无缝转换!冶金级DEVICENET转EtherCAT网关,稳定可靠扛得住!
  • Centos7系统下脚本一键部署LAMP环境
  • Idea 配置 Maven 环境
  • AAOS系列之(七) --- AudioRecord录音逻辑分析(一)
  • Java并发
  • RocketMQ 死信队列(DLQ)实战:原理 + 开发 + 运维 + 架构应用指南
  • 从Java的Jvm的角度解释一下为什么String不可变?
  • 【SpringCache 提供的一套基于注解的缓存抽象机制】
  • 网站资源做缓存/百度提交网站入口
  • 网站定制/全网营销推广怎么做
  • 选择邯郸网站制作/搜索引擎优化关键词
  • wordpress stats view counter/seo分析与优化实训心得
  • 视频源网站怎么做/全国疫情防控最新数据
  • 石碣仿做网站/广州谷歌seo