一些有意思的C++代码

三行情诗

谁说我们写 C++ 的就不懂浪漫了:

// 我的心必须通过"爱你"的约束才能实例化
template<typename Heart> requires loves<Heart, You>
// 激情、忠诚、永恒置入爱的元组,这份爱在编译时就已确定,不会因运行时的变化而改变
// 是写在源码里的承诺,是嵌入二进制的誓言
constexpr auto my_feelings = std::tuple{passion, devotion, eternity};
// 在 C++ 的类型系统中,我们的未来被特化为永远为真,这是编译器都认可的事实
template<> struct future<us> : std::true_type { static constexpr auto value = forever; };

不愧是我们写 C++ 的,写出来的情诗都能是零运行时开销的。

AND

下面这段代码能正常编译运行:

int and a = 1; // 是的你没有看错,这里有个 and...
a = 2; // 是的你还是没有看错,这个 and a 还能被赋值

这一段代码有两个非常有意思的地方,首先第一行的and 本身是一个非常冷门但是古老的关键字,ISO 646 标准引入的——与符号(&&)的别名,早期很多键盘无法输入特殊符号所以引入了这个关键字。

在很多编译环境中,and 被实现成了宏 #define and &&,所以这段代码就变成了:int && a=1&&这个符号还表示右值引用的意思,这里创建了一个对于右值常量 1 的右值引用。

第二行有意思的点可以用一句话描述:”右值引用其实是个左值”,这里就不详细展开了。

CRTP

CRTP(Curiously Recurring Template Pattern),谈奇怪的代码肯定离不开著名的“奇异递归模板模式”,这个技巧在网上被宣传的很高大上:“静态多态”,“vtable killer”,“零开销抽象神迹的体现”,但是实际上我们 99% 场景下的多态是需要将各个实例放在一个容器中的,为了优化虚函数表查找开销,我们也可以使用传统递归代码直接使用子类。

CRTP 更大的意义在于父类(基类模板)可以直接“感知”并利用子类(派生类)的类型信息和具体实现。这使得它能够优雅地实现Mixin功能,为派生类提供通用行为或工具,而不仅仅是传统的“is-a”意义上的继承

template <typename T> struct Base {
  // 这里 T 会传入子类的类型, 父类拿到了子类的类型,这就是递归
  // 直接强转成子类指针调用,就没有虚函数表查找开销
  // Derived1 Derived2 Derived3 在被父类调用时都能无损调用到 do_something_impl
  void do_something() { static_cast<T *>(this)->do_something_impl(); }
};

struct Derived : Base<Derived> {
  void do_something_impl() { std::cout << "Derived impl\n"; }
};

自毁对象

这个类会在构造的一瞬间就离开我们:

struct Kamikaze {
    Kamikaze() { std::cout << "我出生了!\n"; delete this; } 
    ~Kamikaze() { std::cout << "我死了!\n"; }
};
Kamikaze* k = new Kamikaze(); // 输出 “我出生了!” 和 “我死了!”

使用了 delete this语法在对象的内部触发删除自身,在某些情况下可以让对象自己掌控自己的生命周期(我的命运不受他人摆布!),比如事件驱动系统中的自清理监听器、插件系统自我卸载等等。

无敌的对象

C++ 的对象拥有彻底掌控自己命运的能力,既可以选择什么时候死亡,也可以选择拒绝死亡。

#include <iostream>
#include <new>

class Phoenix {
public:
  int i = 42;

  void operator delete(void *) {} // 重写 delete,避免内存被释放,这样就只会执行析构函数但是不会释放内存

  ~Phoenix() { std::cout << "休想杀死我,我是无敌的!!" << std::endl; }
};

int main() {
  Phoenix *p = new Phoenix();
  std::cout << "p->i = " << p->i << std::endl;

  delete p;
  delete p;
  delete p;
  delete p;
  delete p;

  std::cout << "p->i = " << p->i << std::endl; // 对象仍然可以正常使用
}

The Coolest C++ Code

一千个人心中有一千种最酷的 C++ 代码,但是最 Cool 的代码莫过于此:

#include <iostream> 
 
using namespace std; 
int main() 
{ 
  cout << 0 << "K" << endl; // 输出:0K
} 

0K = -273.15℃,绝对零度(Absolute Zero),very cool

一段非常简单的标准库代码

C++ 的标准库是我目前见到的所有编程语言中最复杂的没有之一,且远胜其他编程语言,下面列举一小段 std::vector(动态数组)插入函数的部分相关代码,动态数组相对其他数据结构来说已经是非常简单的了,但是其复杂度仍然相当之高,标准库中充斥着各种多层模板嵌套、历史分支兼容与宏定义,我很难想象一名刚刚学完谭浩强 C++ 的新人不小心点到标准库源代码文件会是什么反应:

template <class _Tp, class _Allocator>
template <class _Up>
_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::pointer
vector<_Tp, _Allocator>::__push_back_slow_path(_Up&& __x) {
  allocator_type& __a = this->__alloc();
  __split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), size(), __a);
  // __v.push_back(std::forward<_Up>(__x));
  __alloc_traits::construct(__a, std::__to_address(__v.__end_), std::forward<_Up>(__x));
  __v.__end_++;
  __swap_out_circular_buffer(__v);
  return this->__end_;
}

template <class _Tp, class _Allocator>
_LIBCPP_CONSTEXPR_SINCE_CXX20 inline _LIBCPP_HIDE_FROM_ABI void
vector<_Tp, _Allocator>::push_back(const_reference __x) {
  pointer __end = this->__end_;
  if (__end < this->__end_cap()) {
    __construct_one_at_end(__x);
    ++__end;
  } else {
    __end = __push_back_slow_path(__x);
  }
  this->__end_ = __end;
}

  template <class _Tp, class... _Args, class = __enable_if_t<__has_construct<allocator_type, _Tp*, _Args...>::value> >
  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static void
  construct(allocator_type& __a, _Tp* __p, _Args&&... __args) {
    _LIBCPP_SUPPRESS_DEPRECATED_PUSH
    __a.construct(__p, std::forward<_Args>(__args)...);
    _LIBCPP_SUPPRESS_DEPRECATED_POP
  }
  template <class _Tp,
            class... _Args,
            class = void,
            class = __enable_if_t<!__has_construct<allocator_type, _Tp*, _Args...>::value> >
  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static void
  construct(allocator_type&, _Tp* __p, _Args&&... __args) {
    std::__construct_at(__p, std::forward<_Args>(__args)...);
  }

太空船运算符

太空船运算符并不是什么用户自创的梗,是实打实的内置语法(since C++ 20),官方名字是(Three-way Comparison Operator)但是真的很神奇!

image-20250617113025934

在没有太空船之间,我们要实现对象完整的比较运算符需要写这么大一堆代码:

struct DateTime {
    int year, month, day, hour, minute, second;
    
    bool operator<(const DateTime& rhs) const {
        if (year != rhs.year) return year < rhs.year;
        if (month != rhs.month) return month < rhs.month;
        if (day != rhs.day) return day < rhs.day;
        if (hour != rhs.hour) return hour < rhs.hour;
        if (minute != rhs.minute) return minute < rhs.minute;
        return second < rhs.second;
    }
    
    bool operator==(const DateTime& rhs) const {
        return year == rhs.year && 
               month == rhs.month && 
               day == rhs.day &&
               hour == rhs.hour && 
               minute == rhs.minute && 
               second == rhs.second;
    }
    
    bool operator!=(const DateTime& rhs) const { return !(*this == rhs); }
    bool operator>(const DateTime& rhs) const { return rhs < *this; }
    bool operator<=(const DateTime& rhs) const { return !(rhs < *this); }
    bool operator>=(const DateTime& rhs) const { return !(*this < rhs); }
};

尽管后面可以用 std::tie或者 std::rel_ops 等各种手段简化,这仍然是一件非常 boring 的事情。

有了太空船以后,这些代码就都能自动生成了:

struct DateTime {
  int year, month, day, hour, minute, second;
  auto operator<=>(const DateTime &) const = default;
};

默认是一个一个按字段自然排序,太空船也是支持自定义比较顺序的:

struct Score {
  int chinese;
  int english;
  int math;
  auto operator<=>(const Score &rhs) const {
    auto lhs_total = chinese + english + math;
    auto rhs_total = rhs.chinese + rhs.english + rhs.math;
    return lhs_total <=> rhs_total; // 将两个结构体的太空船,转换为基本类型的太空船
  }
};

伪随机数

在 C++ 中,如果想要或者一个随机数(伪),除了 std::rand之外,这也是一种方式 :)

会输出什么全取决于内存中残留着什么——也就是不可预知的随机行为。

#include <iostream>
int main() {
  int64_t i;
  std::cout << i++;
}

定义了变量 i 之后只会申请 8 个字节的内存,需要手动赋值才会进行初始化,所以不存在一个默认 0 初始化值。

尽管这很可能非常反直觉,很多其他语言的用户会觉得踩坑了。

但这正是 C++ 核心设计哲学:You don't pay for what you don't use 的体现,在操作系统和硬件层面,申请内存和初始化本来就是两个分开的操作,你只申请了内存,那 C++ 也不会画蛇添足。

万物皆可重载

C++ 里面所有你能想到的运算符都是可以重载的,包括 new delete 甚至是后缀和逗号,至于为什么要重载这么多符号,因为不希望我的类别人拿起来就能用 :)

#include <iostream>
#include <new>

class Everything {
public:
  explicit Everything(int val = 0) : value(val) { log("Constructor"); }

  // 一元运算符重载
  Everything operator+() const { log("+ (unary)"); return *this; }
  Everything operator-() const { log("- (unary)"); return Everything(-value); }
  Everything operator~() const { log("~"); return Everything(~value); }
  bool operator!() const { log("!"); return !value; }
  Everything& operator++() { log("++ (prefix)"); ++value; return *this; }
  Everything operator++(int) { log("++ (postfix)"); Everything temp = *this; ++value; return temp; }
  Everything& operator--() { log("-- (prefix)"); --value; return *this; }
  Everything operator--(int) { log("-- (postfix)"); Everything temp = *this; --value; return temp; }

  // 二元运算符重载
  Everything operator+(const Everything& other) const { log("+ (binary)"); return Everything(value + other.value); }
  Everything operator-(const Everything& other) const { log("- (binary)"); return Everything(value - other.value); }
  Everything operator*(const Everything& other) const { log("*"); return Everything(value * other.value); }
  Everything operator/(const Everything& other) const { log("/"); return Everything(value / other.value); }
  Everything operator%(const Everything& other) const { log("%"); return Everything(value % other.value); }
  Everything operator&(const Everything& other) const { log("& (binary)"); return Everything(value & other.value); }
  Everything operator|(const Everything& other) const { log("|"); return Everything(value | other.value); }
  Everything operator^(const Everything& other) const { log("^"); return Everything(value ^ other.value); }
  Everything operator<<(const Everything& shift) const { log("<<"); return Everything(value << shift.value); }
  Everything operator>>(const Everything& shift) const { log(">>"); return Everything(value >> shift.value); }
  bool operator==(const Everything& other) const { log("=="); return value == other.value; }
  bool operator!=(const Everything& other) const { log("!="); return value != other.value; }
  bool operator<(const Everything& other) const { log("<"); return value < other.value; }
  bool operator>(const Everything& other) const { log(">"); return value > other.value; }
  bool operator<=(const Everything& other) const { log("<="); return value <= other.value; }
  bool operator>=(const Everything& other) const { log(">="); return value >= other.value; }
  bool operator&&(const Everything& other) const { log("&&"); return value && other.value; }
  bool operator||(const Everything& other) const { log("||"); return value || other.value; }

  // 赋值运算符重载
  Everything& operator=(const Everything& other) { log("="); value = other.value; return *this; }
  Everything& operator+=(const Everything& other) { log("+="); value += other.value; return *this; }
  Everything& operator-=(const Everything& other) { log("-="); value -= other.value; return *this; }
  Everything& operator*=(const Everything& other) { log("*="); value *= other.value; return *this; }

  // 特殊运算符重载
  Everything operator,(const Everything& other) const { log(","); return other; }
  Everything& operator[](const Everything& index) { log("[]"); value = index.value; return *this; }
  Everything* operator->() { log("->"); return this; }
  Everything operator->*(Everything* ptr) { log("->*"); return *ptr; }
  int operator()(const Everything& arg) { log("()"); return value + arg.value; }

  // new/delete 重载
  static void* operator new(size_t size) { log("new"); return ::new char[size]; }
  static void operator delete(void* ptr) { log("delete"); ::delete[] static_cast<char*>(ptr); }

  // 隐式转换到int
  operator int() const { log("implicit conversion to int"); return value; }

  int getValue() const { return value; }

  static void log(const char* op) { std::cout << "Overloaded " << op << "!\n"; }

private:
  int value;
};

// UDL重载(扩展多个后缀:_ms, _s, _us)
Everything operator"" _ms(unsigned long long val) {
  Everything::log("literal _ms");
  return Everything(static_cast<int>(val));
}
Everything operator"" _s(unsigned long long val) {
  Everything::log("literal _s");
  return Everything(static_cast<int>(val * 1000));
}
Everything operator"" _us(unsigned long long val) {
  Everything::log("literal _us");
  return Everything(static_cast<int>(val / 1000));
}

int main() {
  Everything e(42);
  auto result = (new Everything((((((e + 42_ms) * e) / e % e & e | e ^ e) << 1_s) >> 1_ms, ((e == e) != (e < e) > (e <= e) >= (e && e) || e), (e += e -= e *= e))[0_us]->operator()(1_ms)  + e++ -- + - ~ ! e));
  std::cout << "Final result: " << result << std::endl;
  return 0;
}

数组下标运算符加法交换律

#include <iostream>

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    
    // 等等...这个 1[arr] 是什么写法?
    std::cout << "The value is: " << 1[arr] << std::endl; 
    // 输出:The value is 20
}

这个是因为数组下标运算符 [] 方括号在底层的实现方式是:a[b] => *(a + b),由于加法运算具有交换性,*(b + a) b 在前面还是后面都是等价的,所以 1[arr] => *(1 + arr) = *(arr + 1) = arr[1]

扩展一下,对于二阶数组来说,这两种写法也是等价的:

arr[2][1] = 1[2[arr]]
int num = 0x123'full; // num == 0x123 * 16 + 15

解析:’ 是个无意义的分隔符,完整的数字是:123f 十六进制,后面的 ull 表示数字格式为 unsigned long long,所以加上这个关键字就是在数字末尾加了个 f,进一位再加 15。

Duff’s Device

Duff's Device是一段非常经典的 C 优化代码,这段代码的作者是 Tom Duff,当他在卢卡斯影业(制作星球大战的公司)工作时,为了榨干一台性能有限的图形终端的全部机能而发明了此方法。

假设我们有一段数据 from要 复制到to 里面,我们是不是很自然的会写出来如下代码:

void send(int* to, int* from, int count) {
    for (int i = 0; i < count; ++i) {
        to[i] = from[i];
    }
}

在几十年前的硬件和编译器环境下,上面这两种写法的循环开销 (loop overhead) 是一个不容忽视的问题。对于每一次拷贝,CPU 都需要执行:

  1. 一次条件判断 (i < countcount > 0)
  2. 一次或多次变量递增/递减 (++icount--)
  3. 一次跳转指令 (跳回循环的开始)

Tom Duff巧妙的利用了 switch 穿透的特性,实现了一个非常高性能的复制版本,在这个版本中,条件判断与跳转指令开销减少到了 18 (之所以是 8 是因为整数计算更快),整体时间复杂度从 O(n) 变为了 O(n/8),堪称最早的手动循环展开(Loop Unrolling)

void duffs_device_send(int* to, int* from, int count) {
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to++ = *from++;
    case 7:      *to++ = *from++;
    case 6:      *to++ = *from++;
    case 5:      *to++ = *from++;
    case 4:      *to++ = *from++;
    case 3:      *to++ = *from++;
    case 2:      *to++ = *from++;
    case 1:      *to++ = *from++;
               } while (--n > 0);
    }
}

当然在 21 世纪的今天,我们已经不需要再去写这种代码了,编译器会帮我们自动完成这一部分工作。

基于 switch 的协程

C++ 和 Python 这些语言的协程并不像 Go 的协程那样高级,本质上都只是一个“可暂停和恢复的函数”,搭配上一些关键字用以插入状态转移点,保存一下栈帧。

所以理论上,我们可以基于 switch + __LINE__实现一个简单的协程,每次调用 COROUTINE_YIELD 生成一个状态转移点,C++ 20 的协程本质上也是这种思路。

#include <iostream>
#include <string>

#define COROUTINE_START switch(state){case 0:
#define COROUTINE_YIELD(x) do{state=__LINE__;return x;case __LINE__:;}while(0)
#define COROUTINE_END }


class ProgressCoroutine {
public:
  ProgressCoroutine(const std::string &name) : name_(name), state(0) {}

  bool Run() {
    COROUTINE_START;
    std::cout << "[" << name_ << "] PROGRESS:25%" << std::endl;
    COROUTINE_YIELD(true);

    std::cout << "[" << name_ << "] PROGRESS:50%" << std::endl;
    COROUTINE_YIELD(true);

    std::cout << "[" << name_ << "] PROGRESS:100%" << std::endl;
    COROUTINE_END;
    return false;
  }

private:
  std::string name_;
  int state;
};

int main() {
  ProgressCoroutine foo("FOO");
  ProgressCoroutine bar("BAR");

  bool foo_running = true, bar_running = true;
  while (foo_running || bar_running) {
    if (foo_running)
      foo_running = foo.Run();
    if (bar_running)
      bar_running = bar.Run();
  }
  std::cout << "ALL FINISHED!" << std::endl;
  return 0;
}

在这个 demo里面 Run()函数的执行被中断了多次才断断续续完成。