为什么可以为不存在的结构创建 typedef?

Why can you create typedefs to a struct that doesn't exist?

以下代码可以正常编译。

header.h:

typedef struct Placeholder_Type* Placeholder;

impl.cpp:

#include "header.h"

void doSomething(Placeholder t) {
  (void) t;
}
    
int main() {
  int *a = new int();
  doSomething((Placeholder)a);
}

编译命令:

clang++ impl.cpp

类型 Placeholder_Type 在任何地方都不存在,并且在输出二进制文件中不作为符号存在。

为什么为不存在的类型创建 typedef 是合法的?

为什么我可以使用不存在的类型创建函数?

这是否等同于仅使用 void* 但命名为“Placeholder”?

为什么不呢?据我所知,“存在”不是一个官方术语。类型可以被声明但不能定义,也可以被声明和定义。在第一种情况下,类型被认为是不完整的。对于不完整的类型,您无能为力。例如,您不能创建该类型的对象。但是,您可以使用指向不完整类型的指针。编译器需要知道的只是类型的声明。进一步注意 typedef 确实包含 Placeholder_Type:

的声明
typedef struct Placeholder_Type* Placeholder; 
        ^---------------------^ declares the class named Placeholder_Type

正如在您的代码中一样,您永远不会创建对象或使用 Placeholder_Type 的成员,它会编译。我不是 100% 确定,但我认为也没有 UB。强制转换 int 看起来不太好,但由于您从未真正取消引用指针,所以没有错。

有关更多信息,请参阅与前向声明非常相关的问题:What are forward declarations in C++? When can I use a forward declaration?

struct Placeholder_Type 声明结构 Placeholder_Type (但不定义它),无论它出现在哪里。即使它在 typedef 内。因此,您不会为不存在的结构创建 typedef,而是为您刚刚声明的结构创建类型定义(如果编译器还不知道它,因此创建)。

至于为什么,是因为这样可以让结构体的定义远离public接口。例如,这是在 C:

中实现不透明对象的典型方法
// mytype.h
typedef struct MytypeImpl* Mytype;

Mytype create_mytype();
void destroy_mytype(Mytype o);

void do_something_with_mytype(Mytype o, int i);

// mytype.c
struct MytypeImpl {
  int something;
  int otherthing;
};
Mytype create_mytype() {
  Mytype o = malloc(sizeof(*o));
  o->something = 0;
  o->otherthing = 0;
  return o;
}
void destroy_mytype(Mytype o) {
  free(o);
}
// etc.

当然,个人风格可能在细节上有所不同。但关键是结构的定义在 mytype.c 之外是不可见的,因此没有人可以访问数据成员。

并非所有声明都同时是定义。

例如,您可以声明一个尚未定义的函数:

void func( void );

然后引用名字func.

或者您可以声明一个 class,例如:

class A
{
    friend class B;
    //...
};

声明在给定范围内引入名称。

只有在评估上下文中使用名称时,它才必须被定义并且具有完整的类型。

在此声明中

typedef struct Placeholder_Type* Placeholder;

引入了两个名字:Placeholder_TypePlaceholder。占位符是指针类型 struct Placeholder_Type* 的别名。指针类型的对象总是完整的对象。您可以使用以下表达式计算其大小:

sizeof( Placeholder )

这与具有指向类型 void 的指针相同。

void *p;

void 类型始终是不完整类型,因为指针类型本身是完整类型。如果您不尝试取消引用指针或应用指针算法,则不需要完整的引用类型。