这其中有几个原因:
- 使用“不透明”指针
- 缺乏析构函数
- 嵌入式系统(堆栈溢出问题)
- 容器
- 惯性
- “懒惰”
让我们简要地讨论它们。
对于不透明指针,它使您可以执行类似以下的操作:
struct CClass_;
typedef struct CClass_ CClass;
// the rest as in your example
因此,用户看不到
struct CClass_
的定义,使其与它的更改隔离开来,并启用其他有趣的功能,例如为不同平台实现类的不同方式。
当然,这禁止使用
CClass
的堆叠变量。但是,另一方面,人们可以看到这并不禁止静态分配
CClass
对象(从某个池返回)-由
CClass_create
或者像
CClass_create_static
这样的另一个函数返回。
缺乏析构函数 - 由于C编译器不会自动销毁您的
CClass
堆栈对象,因此您需要自己进行处理(手动调用析构函数)。因此,唯一剩下的好处是堆栈分配通常比堆分配更快。另一方面,您不必使用堆 - 您可以从池、竞技场或类似的地方进行分配,这可能几乎与堆栈分配一样快,而且没有下面讨论的堆栈分配的潜在问题。
嵌入式系统 - 堆栈不是“无限”的资源,您知道。当然,在今天的“常规”操作系统(POSIX、Windows等)上大多数应用程序几乎是如此。但是,在嵌入式系统上,堆栈可能只有几 KB。这很极端,但即使是“大型”嵌入式系统的堆栈也在MB范围内。因此,如果过度使用,它将耗尽。当它这样做时,大多数情况下没有保证会发生什么 - 就我所知,在C和C++中都是“未定义行为”。另一方面,当内存不足时,
CClass_create()
可以返回空指针,您可以处理它。
容器 - C++用户喜欢堆栈分配,但是,如果您在堆栈上创建一个
std::vector
,它的内容将被分配到堆上。当然,您可以进行调整,但这是默认行为,说“容器的所有成员都是堆分配的”会使人们更容易处理,而不是试图弄清楚如何处理空间不足的情况。
惯性 - 嗯,OO来自SmallTalk。那里一切都是动态的,所以,“自然”的翻译成C的方式是“把所有东西都放在堆上”。因此,最初的示例就是这样的,并在许多年后启发了其他人。
"
懒惰" - 如果您知道自己只想要堆栈对象,则需要类似于:
CClass CClass_make();
void CClass_deinit(CClass *me);
但是,如果你想同时允许堆栈和堆,你需要添加:
CClass *CClass_create();
void CClass_destroy(CClass *me);
对于实施者来说,这意味着更多的工作,但对用户来说也很困惑。虽然可以创建略有不同的接口,但这并不能改变你需要两组函数的事实。
当然,“容器”原因部分上也是“懒惰”的原因。