败犬日报 2025-07-02
1. C++ 和命名参数
现在 C++ 的语法允许函数声明之间的形参名不同,比如:
void foo(int a, int b);
void foo(int b, int a);
所以如果要支持命名参数,不能直接在现有语法上加,肯定要增加新的关键字或符号,这样不够自然(就没动力去做了)。
《C++ 的设计与演化》:
C++ 是面向对象的语言,所以决定不提供这个功能。你的参数如果需要这么写,那请打包成一个对象。
但是这和面向对象没什么关系(C++ 不是纯粹的面向对象语言),只能说面向对象提供了一个方案,别的语法就懒得搞了。
那么做法是什么呢?
经典的面向对象思路:FunBuilder.setA().setB().setC().build()
更推荐的做法是指派初始化:
struct Args {
int a;
int b;
};
void foo(Args args);
foo({.a = 0, .b = 1});
这个指派初始化是 C++20 的,20 之前使用也没什么问题(编译器扩展)。
但是有个缺点是参数必须按顺序初始化。这个需求有一个 linter 下位替代,clang-tidy 的 bugprone-argument-comment 如果要允许乱序的话问题有点大,主要是求值顺序的问题(实参的求值顺序和形参初始化的顺序)。详见乱序指派初始化(就是乱序 { .x = y}
)提案 https://wg21.link/p3405
最后还有个炫技的命名参数实现 https://godbolt.org/z/16EWPnxfT(看一乐)。
2. mov是图灵完备的
mov是图灵完备的 - 攻伤菊菊长的文章 https://zhuanlan.zhihu.com/p/1923400343963820102
甚至还有 c 语言到 mov 的编译器 https://github.com/xoreaxeaxeax/movfuscator
3. goto 会自动析构
#include <iostream>
class A {
public:
A() { std::cout << "A constructor called" << std::endl; }
~A() { std::cout << "A destructor called" << std::endl; }
};
int main() {
int cnt = 0;
label:
A a;
if (++cnt < 2) {
goto label;
}
std::cout << "Exiting main function" << std::endl;
return 0;
}
输出是:
A constructor called
A destructor called
A constructor called
Exiting main function
A destructor called
很符合直觉。
4. 状态管理,用全局变量实现容易堆屎山
大家的共识是函数参数传 context。
但是如果整个程序用同一个 context 也会堆屎山,要保证 context 被使用的范围足够小。
还有 context 的一个问题是成员的生命周期,什么时候有值,什么时候没有值。一般只能靠注释,没有别的好办法。