Skip to content

败犬日报 2025-07-02

1. C++ 和命名参数

现在 C++ 的语法允许函数声明之间的形参名不同,比如:

cpp
void foo(int a, int b);

void foo(int b, int a);

所以如果要支持命名参数,不能直接在现有语法上加,肯定要增加新的关键字或符号,这样不够自然(就没动力去做了)。


《C++ 的设计与演化》:

C++ 是面向对象的语言,所以决定不提供这个功能。你的参数如果需要这么写,那请打包成一个对象。

但是这和面向对象没什么关系(C++ 不是纯粹的面向对象语言),只能说面向对象提供了一个方案,别的语法就懒得搞了。


那么做法是什么呢?

经典的面向对象思路:FunBuilder.setA().setB().setC().build()

更推荐的做法是指派初始化:

cpp
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 会自动析构

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

输出是:

text
A constructor called
A destructor called
A constructor called
Exiting main function
A destructor called

很符合直觉。

4. 状态管理,用全局变量实现容易堆屎山

大家的共识是函数参数传 context。

但是如果整个程序用同一个 context 也会堆屎山,要保证 context 被使用的范围足够小。

还有 context 的一个问题是成员的生命周期,什么时候有值,什么时候没有值。一般只能靠注释,没有别的好办法。