代码之家  ›  专栏  ›  技术社区  ›  splicer

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

  •  42
  • splicer  · 技术社区  · 14 年前

    我已经看到以下两种在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;
    };
    
    3 回复  |  直到 6 年前
        1
  •  66
  •   R.. GitHub STOP HELPING ICE    14 年前

    我的投票是关于第三个选项,穆维吉尔发表后删除:

    我看到了第三条路:

    // foo.h
    struct foo;
    void doStuff(struct foo *f);
    
    // foo.c
    struct foo {
        int x;
        int y;
    };
    

    如果你真的不能忍受打字 struct 关键字, typedef struct foo foo; (注意:去掉无用和有问题的下划线)是可以接受的。但不管你做什么, 从未 使用 typedef 定义指针类型的名称。它隐藏了一条非常重要的信息,即这种类型的变量引用了一个对象,当您将其传递给函数时,可以对其进行修改,并使处理具有不同限定条件(例如, const -合格的)指针版本是一大痛苦。

        2
  •  1
  •   tijko    10 年前

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

    因此,我倾向于选择2。也就是说,所呈现的接口类型是可以在每个间接级别上指定cv ness的接口类型。当然一个 可以 回避选项1库编写器,只需使用 foo 当库编写器更改实现时,会让自己陷入各种恐惧。(也就是说,选项1库编写器只感知到 fooRef 是不变接口的一部分, 可以来,去,改变,无论什么。选项2图书馆作者认为 是不变接口的一部分。)

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

        3
  •  0
  •   Gabriel Staples    6 年前

    选项1.5

    我习惯用 选项1 ,除非您将引用命名为 _h 表示它是这个给定的C“类”的C样式“对象”的“句柄”。然后,确保您的函数原型使用 const 只要这个对象的内容“handle”只是一个输入,不能更改,也不能使用 康斯特 无论内容在哪里 可以 被改变。

    下面是一个完整的例子:

    //======================================================================================================================
    // 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. 实现完全错误处理并返回错误而不是 void .
    2. 添加名为的配置结构 my_module_config_t 到.h文件,并将其传递到 open 函数在创建新对象时更新内部变量。例子:

      //--------------------
      // my_module.h
      //--------------------
      
      // my_module configuration struct
      typedef struct my_module_config_s
      {
          int my_config_param_int;
          int my_config_param_float;
      } my_module_config_t;
      
      void my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config);
      
      //--------------------
      // my_module.c
      //--------------------
      
      void my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config)
      {
          // 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 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:
      }