选项 1.5(“基于对象”的 C 架构):
我习惯于使用Option 1,除非您将引用命名_h
为表示它是给定 C“类”的 C 样式“对象”的“句柄”。然后,您确保您的函数原型使用const
该对象“句柄”的内容仅为输入且不能更改的任何地方,并且不要使用可以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;
void doStuff1(my_module_h my_module);
void doStuff2(const my_module_h my_module);
// -------------
// my_module.c
// -------------
// Definition of the opaque struct "object" of C-style "class" "my_module".
struct my_module_s
{
int int1;
int int2;
float f1;
// etc. etc--add more "private" member variables as you see fit
};
这是在 C 中使用不透明指针创建对象的完整示例。以下架构可能被称为“基于对象的 C”:
//==============================================================================================
// 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.
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)
// Dynamically allocate memory for the full contents of the struct "object"
my_module = malloc(sizeof(*my_module));
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:
}
简化示例用法:
#include "my_module.h"
#include <stdbool.h>
#include <stdio.h>
int main()
{
printf("Hello World\n");
bool exit_now = false;
// setup/initialization
my_module_h my_module = NULL;
// For safety-critical and real-time embedded systems, it is **critical** that you ONLY call
// the `_open()` functions during **initialization**, but NOT during normal run-time,
// so that once the system is initialized and up-and-running, you can safely know that
// no more dynamic-memory allocation, which is non-deterministic and can lead to crashes,
// will occur.
my_module_open(&my_module);
// Ensure initialization was successful and `my_module` is no longer NULL.
if (!my_module)
{
// await connection of debugger, or automatic system power reset by watchdog
log_errors_and_enter_infinite_loop();
}
// run the program in this infinite main loop
while (exit_now == false)
{
my_module_do_stuff1(my_module);
my_module_do_stuff2(my_module);
}
// program clean-up; will only be reached in this case in the event of a major system
// problem, which triggers the infinite main loop above to `break` or exit via the
// `exit_now` variable
my_module_close(my_module);
// for microcontrollers or other low-level embedded systems, we can never return,
// so enter infinite loop instead
while (true) {}; // await reset by watchdog
return 0;
}
除此之外,唯一的改进是:
实现完整的错误处理并返回错误而不是void
. 前任:
/// @brief my_module error codes
typedef enum my_module_error_e
{
/// No error
MY_MODULE_ERROR_OK = 0,
/// Invalid Arguments (ex: NULL pointer passed in where a valid pointer is required)
MY_MODULE_ERROR_INVARG,
/// Out of memory
MY_MODULE_ERROR_NOMEM,
/// etc. etc.
MY_MODULE_ERROR_PROBLEM1,
} my_module_error_t;
现在,不是void
在上面和下面的所有函数中返回一个类型,my_module_error_t
而是返回一个错误类型!
在 .h 文件中添加一个调用的配置结构my_module_config_t
,并将其传递给open
函数以在创建新对象时更新内部变量。这有助于将所有配置变量封装在单个结构中,以便在调用_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)
// Dynamically allocate memory for the full contents of the struct "object"
my_module = malloc(sizeof(*my_module));
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. Set these
// private variables inside `my_module` to whatever they need to be. You get the idea...
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 handle to the user, and exit.
*my_module_h_p = my_module;
done:
return err;
}
和用法:
my_module_error_t err = MY_MODULE_ERROR_OK;
my_module_h my_module = NULL;
my_module_config_t my_module_config =
{
.my_config_param_int = 7,
.my_config_param_float = 13.1278,
};
err = my_module_open(&my_module, &my_module_config);
if (err != MY_MODULE_ERROR_OK)
{
switch (err)
{
case MY_MODULE_ERROR_INVARG:
printf("MY_MODULE_ERROR_INVARG\n");
break;
case MY_MODULE_ERROR_NOMEM:
printf("MY_MODULE_ERROR_NOMEM\n");
break;
case MY_MODULE_ERROR_PROBLEM1:
printf("MY_MODULE_ERROR_PROBLEM1\n");
break;
case MY_MODULE_ERROR_OK:
// not reachable, but included so that when you compile with
// `-Wall -Wextra -Werror`, the compiler will fail to build if you forget to handle
// any of the error codes in this switch statement.
break;
}
// Do whatever else you need to in the event of an error, here. Ex:
// await connection of debugger, or automatic system power reset by watchdog
while (true) {};
}
// ...continue other module initialization, and enter main loop
也可以看看:
- [我的另一个答案引用了我上面的答案] C 中不透明结构和数据隐藏的架构考虑和方法
关于基于对象的 C 架构的补充阅读:
- 在推出自己的结构时提供辅助功能
goto
对专业代码错误处理的有效使用的附加阅读和理由:
goto
支持在 C 中使用错误处理的论点: https ://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/blob/master/Research_General/goto_for_error_handling_in_C/readme.md
- *****优秀文章展示了在 C 中使用错误处理的优点
goto
:“在 C 中使用 goto 进行错误处理” - https://eli.thegreenplace.net/2009/04/27/using-goto-for- c 中的错误处理
- 在 C 中有效地使用 goto 进行错误管理?
- C 代码中的错误处理
搜索词以使其更易于搜索:C 中的不透明指针、C 中的不透明结构、C 中的 typedef 枚举、C 中的错误处理、c 体系结构、基于对象的 c 体系结构、c 中初始化体系结构时的动态内存分配