不透明的C结构:它们应该如何声明?

时间:2010-10-19 04:13:37

标签: c coding-style struct typedef opaque-pointers

我在C API中看到了以下两种声明opaque类型的样式。使用一种风格而不是另一种风格有明显的优势吗?

选项1

// foo.h
typedef struct foo * fooRef;
void doStuff(fooRef f);

// foo.c
struct foo {
    int x;
    int y;
};

选项2

// foo.h
typedef struct _foo foo;
void doStuff(foo *f);

// foo.c
struct _foo {
    int x;
    int y;
};

3 个答案:

答案 0 :(得分:66)

我的投票是针对mouviciel发布的第三个选项,然后删除:

  

我见过第三种方式:

// foo.h
struct foo;
void doStuff(struct foo *f);

// foo.c
struct foo {
    int x;
    int y;
};

如果你真的无法忍受输入struct关键字,typedef struct foo foo;(注意:摆脱无用且有问题的下划线)是可以接受的。但无论你做什么,从不使用typedef来定义指针类型的名称。它隐藏了极其重要的信息,即这种类型的变量引用了一个对象,只要将它们传递给函数就可以修改它,并且它使得处理不同限定(例如,const - 限定的)版本的指针是一个重大的痛苦。

答案 1 :(得分:1)

bar(const fooRef)声明一个不可变的地址作为参数。 bar(const foo *)声明一个不可变foo的地址作为参数。

出于这个原因,我倾向于选择选项2.即,所呈现的接口类型是可以在每个间接级别指定cv-ness的接口类型。当然,一个可以回避选项1库编写器并只使用foo,当库编写器更改实现时,打开各种各样的恐怖。 (即,选项1库编写器只感知fooRef是不变接口的一部分,foo可以来,去,被改变等等。选项2库编写者认为{{1} }是不变界面的一部分。)

我更惊讶的是没有人建议组合typedef / struct结构 foo

答案 2 :(得分:0)

选项1.5

我习惯使用选项1 ,除非您用_h命名引用以表示它是给定C的C样式“对象”的“句柄” “类”。然后,确保函数原型在此对象“句柄”的内容仅作为输入且不能更改的地方使用const,并且在内容可以< / em>进行更改。

这是一个完整的例子:

const

除此之外,唯一的改进是:

  1. 实施完整的错误处理并返回错误,而不是//====================================================================================================================== // my_module.h //====================================================================================================================== // An opaque pointer (handle) to a C-style "object" of "class" type "my_module" (struct my_module_s *, or my_module_h): typedef struct my_module_s *my_module_h; // Create a new "object" of "class" "my_module": // A function that takes a *pointer to* an "object" handle, `malloc`s memory for a new copy of the opaque // `struct my_module_s`, then points the user's input handle (via its passed-in pointer) to this newly-created // "object" of "class" "my_module". void my_module_open(my_module_h * my_module_h_p); // A function that takes this "object" (via its handle) as an input only and cannot modify it void my_module_do_stuff1(const my_module_h my_module); // A function that can modify the private content of this "object" (via its handle) (but still cannot modify the // handle itself) void my_module_do_stuff2(my_module_h my_module); // Destroy the passed-in "object" of "class" type "my_module": // A function that can close this object by stopping all operations, as required, and `free`ing its memory. // `struct my_module_s`, then points the user's input handle (via its passed-in pointer) to this newly-created "object". void my_module_close(my_module_h my_module); //====================================================================================================================== // my_module.c //====================================================================================================================== // Definition of the opaque struct "object" of C-style "class" "my_module". // - NB: Since this is an opaque struct (declared in the header but not defined until the source file), it has the // following 2 important properties: // 1) It permits data hiding, wherein you end up with the equivalent of a C++ "class" with only *private* member // variables. // 2) Objects of this "class" can only be dynamically allocated. No static allocation is possible since any module // including the header file does not know the contents of *nor the size of* (this is the critical part) this "class" // (ie: C struct). struct my_module_s { int my_private_int1; int my_private_int2; float my_private_float; // etc. etc--add more "private" member variables as you see fit } void my_module_open(my_module_h * my_module_h_p) { // Ensure the passed-in pointer is not NULL (since it is a core dump/segmentation fault to try to dereference // a NULL pointer) if (!my_module_h_p) { // Print some error or store some error code here, and return it at the end of the function instead of // returning void. goto done; } // Now allocate the actual memory for a new my_module C object from the heap, thereby dynamically creating this // C-style "object". my_module_h my_module; // Create a local object handle (pointer to a struct) my_module = malloc(sizeof(*my_module)); // Dynamically allocate memory for the full contents of the struct "object" if (!my_module) { // Malloc failed due to out-of-memory. Print some error or store some error code here, and return it // at the end of the function instead of returning void. goto done; } // Initialize all memory to zero (OR just use `calloc()` instead of `malloc()` above!) memset(my_module, 0, sizeof(*my_module)); // Now pass out this object to the user, and exit. *my_module_h_p = my_module; done: } void my_module_do_stuff1(const my_module_h my_module) { // Ensure my_module is not a NULL pointer. if (!my_module) { goto done; } // Do stuff where you use my_module private "member" variables. // Ex: use `my_module->my_private_int1` here, or `my_module->my_private_float`, etc. done: } void my_module_do_stuff2(my_module_h my_module) { // Ensure my_module is not a NULL pointer. if (!my_module) { goto done; } // Do stuff where you use AND UPDATE my_module private "member" variables. // Ex: my_module->my_private_int1 = 7; my_module->my_private_float = 3.14159; // Etc. done: } void my_module_close(my_module_h my_module) { // Ensure my_module is not a NULL pointer. if (!my_module) { goto done; } free(my_module); done: }
  2. 在.h文件中添加一个名为void的配置结构,并将其传递给my_module_config_t函数,以在创建新对象时更新内部变量。示例:

    open