Skip to content

败犬日报 2025-04-02

1. 继承中的 public private 影响内存布局

cpp
#include <iostream>

struct A1 {
   private:
    int x;
    int y;
    char o;
};

struct B1 : public A1 {
   public:
    char z;
};

struct A2 {
   public:
    int x;
    int y;
    char o;
};

struct B2 : public A2 {
   public:
    char z;
};

int main() {
    std::cout << sizeof(A1) << "\n";
    std::cout << sizeof(B1) << "\n";
    std::cout << sizeof(A2) << "\n";
    std::cout << sizeof(B2) << "\n";
}

GCC / Clang 输出 12 12 12 16,MSVC 输出 12 16 12 16(MSVC 很合理,所以这里只讨论 GCC / Clang)。


这个问题简单来讲就是,我们不要依赖非标准的布局。

这里 B1, B2 都不是标准布局,原因是标准布局有一个要求“继承层级中仅有一个类具有非静态数据成员”(https://zh.cppreference.com/w/cpp/language/classes),B1 的继承层级有两个类有成员(即 A1 和 B1),B2 同理,不符合这个要求。


那为什么 B1, B2 大小不同呢?原因藏在 Itanium ABI 里。

C++98 定义 POD 要求数据成员都是 public,到了 C++11 才允许 private / protected。而 Itanium ABI 是按照 C++03 对 POD 的定义来的,并且规定了 POD 的 tail padding 不能用于其他用途。https://itanium-cxx-abi.github.io/cxx-abi/abi.html#pod 的这句话:

We ignore tail padding for PODs because the standard before the resolution of CWG issue 43 did not allow us to use it for anything else and because it sometimes permits faster copying of the type.

翻译:我们忽略 POD 的尾部填充,因为在解决 CWG 问题 43 之前的标准不允许我们将其用于其他任何用途,并且它有时允许更快地复制类型。

因为 A1 有 private 数据成员,即不是 C++03 的 POD,不受 Itanium ABI 的 POD 约束,所以编译器可以为了节省空间把 B1 的内存塞到 tail padding 里(char z 紧跟在 char o 后面)。A2 是 C++03 的 POD,所以 B2 不能复用 A2 的内存(char z 必须单独占 4 字节)。


public private 会影响内存布局,这个很反直觉啊。C++ 的坑太多了。

2. CRTP 基类不能用 requires 对子类模板做约束

cpp
template <class T>
    requires requires(T obj) { obj.f(); }  // 报错
struct A {
};

struct B : A<B> {
    void f() {}
};

因为这个阶段 B 是不完整类型,需要把 requires 放进 A 的大括号里。

3. CRTP 有什么用

可以理解为对依赖子类的函数注入方法。

例:

  1. 标准库 std::enable_shared_from_this
  2. 典型应用案例 visitor 模式。