【问题标题】:Function Prefix vs "Function Struct" in CC中的函数前缀与“函数结构”
【发布时间】:2019-02-24 11:18:57
【问题描述】:

我正在尝试找出 C(11) 中的最佳实践。因为没有命名空间,所以我可以想到两种避免名称冲突的方法:

a.) 函数前缀

void kernel_init(void) { ... }
int kernel_start(void* foo) { ... }

b.) “函数结构”

struct kernel {
    void (*init)(void);
    int (*start)(void* foo);
} kernel;

我不是在问哪种方法更漂亮,因为那是相当主观的。我要问的是除了代码风格之外,这两种方法有什么明显的缺点吗?这包括一开始不相关但随着代码库增长而成为更大问题的小事情。

【问题讨论】:

  • 如何命名要分配给结构成员的函数?
  • @Gerhardh 好点!由于它们仅在源文件中可见,因此并不重要(但我会使用前缀)
  • 用结构体处理函数的另一个问题是编译器不能再内联这些函数了!
  • 第二种方式在C中常用来实现动态调度(相当于C++中的虚方法)。但是额外的开销通常会阻碍它在任何其他情况下的使用。

标签: c function struct prefix


【解决方案1】:

函数版本是最常用的。

struct 版本的缺点是它不是独立的。函数指针应该从“构造函数”设置,而不是从调用者设置,因为这会违反私有封装设计实践。你会给你的构造函数起什么名字?无论如何,你都需要一个前缀函数。

当您希望实现多态性或具有回调函数时,通常只使用这样的结构。一些编码风格也使用结构来模拟 C++ 类成员,但这是否真的是好的做法是有争议的。

最佳 OO 实践需要第三个版本,使用不透明指针,这意味着您需要函数和结构:

typedef struct kernel_t kernel_t;

kernel_t* kernel_init (void) { ... }
int kernel_start (kernel_t* this, ...) { ... }

其中结构定义仅对“kernel.c”可见,对调用者不可见。

【讨论】:

    【解决方案2】:

    有趣的是,我以前从未想过这种解决方案。

    第一个当然是标准的,我敢打赌你会在绝大多数 C 项目中找到它[*]。

    理论上第二个占用内存,因为您实际上是在声明一个充满函数指针的数据对象。当然,您还需要初始化 kernel 变量,例如:

    ...
    } kernel = {
      .init = kernel_init,
      .start = kernel_start,
    };
    

    但是哦,你又可以使用前缀函数了。为了消除对这些的需求,函数必须是static,我想如果你在kernel.hstruct 声明中添加extern 是可能的。

    所以一个更完整的例子可能是:

    // kernel.h (public header)
    typedef struct kernel_api {
      void (*init)(void);
      int (*start)(void* foo);
    } kernel_api;
    
    extern const kernel_api kernel;
    
    // in kernel.c
    static void init(void)
    {
      ...
    }
    
    static int start(void *foo)
    {
      ..
    }
    
    const kernel_api kernel = {
      .init = init,
      .start = start,
    };
    

    这可能有效,但我还没有尝试过。

    最后,拥有显式数据意味着需要一个足够智能的编译器来优化这些数据并进行更直接的调用,但我还没有尝试过,依赖这样有点冒险。很有趣。

    [*] 我想我只是在统计上声称我已经看过(或想到过)世界上绝大多数的 C 项目,但这当然不是真的。 :)

    【讨论】:

    • 这是一个公平的观点。在我的天真中,我只是假设调用结构中的函数和调用普通函数是相同的,因为它们是在编译时初始化的,因此编译器会像普通函数调用一样知道它们的地址
    • 我想这就解决了。函数前缀更好(消耗的内存更少,并且可能更快,假设编译器无法将 struct 方法转换为直接函数调用)并且因为无论如何它是一个完全风格的决定,这就是我将要使用的!
    • static 函数还有另一个问题 => 您的 kernel.c 文件将包含可能未清理的文件,因此您面临@ 声明冲突的风险987654330@、start等...
    猜你喜欢
    • 2012-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-10
    相关资源
    最近更新 更多