编码风格-不透明的C结构:应如何声明?

我已经看到了以下两种在C API中声明不透明类型的样式。 使用一种样式相对于另一种样式有明显的优势吗?

选项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;
};
splicer asked 2020-06-29T23:17:19Z
3个解决方案
71 votes

我的投票赞成mouviciel发布并删除的第三个选项:

我看过第三种方式:

struct

如果您真的不能忍受键入struct关键字,则可以选择typedef struct foo foo;(请注意:删除无用和有问题的下划线)。 但是无论您做什么,都不要使用typedef来定义指针类型的名称。 它隐藏了极其重要的信息,该类型的变量引用了可以在您将其传递给函数时进行修改的对象,并且这使得处理不同限定(例如const)指针的版本成为一个主要难题。 。

R.. answered 2020-06-29T23:17:37Z
1 votes

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

出于这个原因,我倾向于选择选项2。即,显示的接口类型是可以在每个间接级别指定cv-ness的接口类型。 当然,可以避开选项1库编写器,而只需使用typedef struct { ... } foo;,当库编写器更改实现时,您会感到各种各样的恐惧。 (即,选项1库编写器仅感知到fooRef是不变接口的一部分,而foo可以来,去,被改变等。选项2库编写器感知到foo是不变接口的一部分。)

令我惊讶的是,没有人建议将typedef / struct组合使用。
typedef struct { ... } foo;

Eric Towers answered 2020-06-29T23:18:11Z
0 votes

选项1.5

我习惯于使用选项1,除非您用//-------------------- // my_module.h //-------------------- // my_module configuration struct typedef struct my_module_config_s { int my_config_param_int; float my_config_param_float; } my_module_config_t; my_module_error_t my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config); //-------------------- // my_module.c //-------------------- my_module_error_t my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config) { my_module_error_t err = MY_MODULE_ERROR_OK; // 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. Ex: err = MY_MODULE_ERROR_INVARG; 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. Ex: err = MY_MODULE_ERROR_NOMEM; goto done; } // Initialize all memory to zero (OR just use `calloc()` instead of `malloc()` above!) memset(my_module, 0, sizeof(*my_module)); // Now initialize the object with values per the config struct passed in. my_module->my_private_int1 = config->my_config_param_int; my_module->my_private_int2 = config->my_config_param_int*3/2; my_module->my_private_float = config->my_config_param_float; // etc etc // Now pass out this object to the user, and exit. *my_module_h_p = my_module; done: return err; } 命名引用以表示它是此给定C“类”的C样式“对象”的“句柄”。 然后,确保函数原型在此对象“句柄”的内容仅是输入且不能更改的地方使用open,并且在可以更改内容的地方不要使用const

这是一个完整的示例:

//======================================================================================================================
// 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:
}

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

  1. 实现完整的错误处理并返回错误,而不是//-------------------- // my_module.h //-------------------- // my_module configuration struct typedef struct my_module_config_s { int my_config_param_int; float my_config_param_float; } my_module_config_t; my_module_error_t my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config); //-------------------- // my_module.c //-------------------- my_module_error_t my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config) { my_module_error_t err = MY_MODULE_ERROR_OK; // 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. Ex: err = MY_MODULE_ERROR_INVARG; 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. Ex: err = MY_MODULE_ERROR_NOMEM; goto done; } // Initialize all memory to zero (OR just use `calloc()` instead of `malloc()` above!) memset(my_module, 0, sizeof(*my_module)); // Now initialize the object with values per the config struct passed in. my_module->my_private_int1 = config->my_config_param_int; my_module->my_private_int2 = config->my_config_param_int*3/2; my_module->my_private_float = config->my_config_param_float; // etc etc // Now pass out this object to the user, and exit. *my_module_h_p = my_module; done: return err; }

    //--------------------
    // my_module.h
    //--------------------
    
    // my_module configuration struct
    typedef struct my_module_config_s
    {
        int my_config_param_int;
        float my_config_param_float;
    } my_module_config_t;
    
    my_module_error_t my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config);
    
    //--------------------
    // my_module.c
    //--------------------
    
    my_module_error_t my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config)
    {
        my_module_error_t err = MY_MODULE_ERROR_OK;
    
        // 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. Ex:
            err = MY_MODULE_ERROR_INVARG;
            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. Ex:
            err = MY_MODULE_ERROR_NOMEM;
            goto done;
        }
    
        // Initialize all memory to zero (OR just use `calloc()` instead of `malloc()` above!)
        memset(my_module, 0, sizeof(*my_module));
    
        // Now initialize the object with values per the config struct passed in.
        my_module->my_private_int1 = config->my_config_param_int;
        my_module->my_private_int2 = config->my_config_param_int*3/2;
        my_module->my_private_float = config->my_config_param_float;        
        // etc etc
    
        // Now pass out this object to the user, and exit.
        *my_module_h_p = my_module;
    
    done:
        return err;
    }
    

    现在,不要在上面和下面的所有函数中都返回2944988727262839839808类型,而是返回open错误类型!

  2. 将名为//-------------------- // my_module.h //-------------------- // my_module configuration struct typedef struct my_module_config_s { int my_config_param_int; float my_config_param_float; } my_module_config_t; my_module_error_t my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config); //-------------------- // my_module.c //-------------------- my_module_error_t my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config) { my_module_error_t err = MY_MODULE_ERROR_OK; // 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. Ex: err = MY_MODULE_ERROR_INVARG; 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. Ex: err = MY_MODULE_ERROR_NOMEM; goto done; } // Initialize all memory to zero (OR just use `calloc()` instead of `malloc()` above!) memset(my_module, 0, sizeof(*my_module)); // Now initialize the object with values per the config struct passed in. my_module->my_private_int1 = config->my_config_param_int; my_module->my_private_int2 = config->my_config_param_int*3/2; my_module->my_private_float = config->my_config_param_float; // etc etc // Now pass out this object to the user, and exit. *my_module_h_p = my_module; done: return err; } 的配置结构添加到.h文件,并将其传递给open函数,以在创建新对象时更新内部变量。 例:

    //--------------------
    // my_module.h
    //--------------------
    
    // my_module configuration struct
    typedef struct my_module_config_s
    {
        int my_config_param_int;
        float my_config_param_float;
    } my_module_config_t;
    
    my_module_error_t my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config);
    
    //--------------------
    // my_module.c
    //--------------------
    
    my_module_error_t my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config)
    {
        my_module_error_t err = MY_MODULE_ERROR_OK;
    
        // 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. Ex:
            err = MY_MODULE_ERROR_INVARG;
            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. Ex:
            err = MY_MODULE_ERROR_NOMEM;
            goto done;
        }
    
        // Initialize all memory to zero (OR just use `calloc()` instead of `malloc()` above!)
        memset(my_module, 0, sizeof(*my_module));
    
        // Now initialize the object with values per the config struct passed in.
        my_module->my_private_int1 = config->my_config_param_int;
        my_module->my_private_int2 = config->my_config_param_int*3/2;
        my_module->my_private_float = config->my_config_param_float;        
        // etc etc
    
        // Now pass out this object to the user, and exit.
        *my_module_h_p = my_module;
    
    done:
        return err;
    }
    

有关基于对象的C体系结构的其他阅读:

  • 推出自己的结构时提供辅助功能
Gabriel Staples answered 2020-06-29T23:19:08Z
translate from https://stackoverflow.com:/questions/3965279/opaque-c-structs-how-should-they-be-declared