C中vtable的单独结构

Separate struct for vtable in C

在 C 中执行 OOP 的自然方法是执行如下操作:

#include <stddef.h>
/* stream operations */
struct _src {
  /** open stream. Return true on success */
  bool (*open)(struct _src *src, const char *fspec);
  /** close stream. Return true on success */
  bool (*close)(struct _src *src);
  /** Returns the actual size of buffer read (<=len), or 0 upon error */
  size_t (*read)(struct _src *src, void *buf, size_t len);
  /* hold the actual FILE* in the case of file stream */
  void *data;
};

有时我看到操作在一个单独的结构中,并且这样设计:

#include <stddef.h>
struct _src {
  /* vtable */
  const struct _src_ops *ops;
  /* hold the actual FILE* in the case of file stream */
  void *data;
};

/* stream operations */
struct _src_ops {
  /** open stream. Return true on success */
  bool (*open)(struct _src *src, const char *fspec);
  /** close stream. Return true on success */
  bool (*close)(struct _src *src);
  /** Returns the actual size of buffer read (<=len), or 0 upon error */
  size_t (*read)(struct _src *src, void *buf, size_t len);
};

这样做的意义何在?

似乎有两个好处:

  • const 限定符阻止用户有意或无意地使用 re-directing 函数指针。这将使函数指针本身成为 read-only.
  • 有一个指针允许函数成为一个单独的“单例”实例,因为这些函数对于每个结构对象都是相同的并且只需要分配一次。

但是,使用函数指针执行 OOP 是相当粗糙的,并且在您完全确定需要多态性的情况下大多才有意义。没有 this 指针,所以你最终会得到像 foo.func(&foo, ...) 这样笨拙的语法。值得注意的是,编写“对象点成员”的可能性是一种语言语法,与 OO 设计本身无关。

总的来说,花费大量精力来实现 inheritance/polymorphism 有点罕见且专门的 OO 概念,同时却忽略了私有封装这一更为重要的 OO 概念,这很奇怪。

根据我的经验,最好只使用普通函数但实现 不透明类型 并将实例作为参数传递给结构。在真正需要时处理继承。

您可能想考虑 prototype-based 继承与基于 class 的继承

的 vtable 的两种“样式”

第一种方法类似于Java脚本prototype-based继承。通过修改实例 vtable 可以更轻松地更改单个实例的行为。更改某个 'class'.

的所有对象的行为并非易事

第二种方法类似于传统的 C++(和 Java)继承。它使更改某个 class 的所有对象的行为变得更容易(通过修改共享 vtable),但它使更改单个实例的行为稍微复杂一些:在这种情况下,一个新的需要创建(从原始vtable复制)vtable,修改并分配给特定实例。

根据您的应用,您可以选择正确的实现。

假设不需要为特定实例分配行为,也不需要动态修改class行为(初始创建后),考虑以下因素来做决定:

  • 共享 vtable 的每次调用成本略高(由于需要访问 vtable 与指针)
  • 共享 vtable 会导致内存利用率较低(不需要 per-instance vtable)
  • 共享 vtable 将导致更快的对象创建(无需为每个实例初始化 vtable)。

查看更多内容:prototype based vs. class based inheritance