C语言 在推出自己的结构时提供帮助程序函数



如果我正在开发一个 C 共享库并且我有自己的结构。为了使库使用者更容易对这些结构实例进行常见操作,是否可以在结构本身中提供指向此类函数的函数指针?这是一种好的做法吗?在多线程方面是否存在问题,其中实用程序函数与不同的参数并行调用等等?

我知道它更接近C++类,但我希望坚持使用 C 并学习如何在过程语言而不是 OOP 中完成它。

举个例子

typedef struct tag tag;
typedef struct my_custom_struct my_custom_struct;
struct tag
{
// ...
};
struct my_custom_struct
{
tag *tags;
my_custom_struct* (*add_tag)(my_custom_struct* str, tag *tag);  
};
my_custom_struct* add_tag(my_custom_struct* str, tag *tag)
{
// ...
}

其中add_tag是一个帮助程序,它设法将标签添加到 *str 中的标签列表中。 我在libjson-c中看到这种模式,就像这里一样 - http://json-c.github.io/json-c/json-c-0.13.1/doc/html/structarray__list.html。array_list内部给出了一个函数指针来帮助释放它。

使库更容易对这些结构实例进行常见操作 消费者,我可以在里面提供指向此类函数的函数指针吗 结构本身?

可以为结构赋予作为函数指针的成员,这些成员指向其参数包含指向结构类型的指针的函数类型,并且这些函数类型或多或少地用于C++实例方法,或多或少如问题中所述。

这是一种好的做法吗?

TL;大卫:没有。

您将遇到的第一个问题是正确初始化这些指针成员。 尽管名称对应,但结构实例中的函数指针不会自动初始化为指向特定函数。除非你使结构类型不透明,否则用户可以(并且无疑有时会)声明实例,而无需调用你为此目的提供的任何构造函数-模拟函数,然后混乱就会随之而来。

如果您确实使结构不透明(毕竟这不是一个坏主意),那么您无论如何都需要非成员函数,因为您的用户将无法直接访问函数指针。 也许是这样的:

struct my_custom_struct *my_add_tag(struct my_custom_struct *str, tag *tag) {
return str->add_tag(str, tag);
}

但是,如果您要提供这一点,那么额外的间接级别有什么意义呢? (答:唯一好的原因是,在不同的实例中,函数指针可以指向不同的函数。

如果您不使结构不透明,则类似情况也适用。 然后你可能会认为用户会(更多)直接调用

str->add_tag(str, tag);

但究竟是什么使它成为一种便利

add_tag(str, tag);

所以总的来说,不,我一般不会认为这种方法是一种好的做法。 在有限的情况下,按照这些思路做一些事情可能是有意义的,但不是作为一般的图书馆惯例。

会有问题吗 关于调用实用程序函数的多线程 平行于不同的参数等等?

不比以任何其他方式指定的函数更是如此,除非函数指针本身正在被修改。

我知道它更接近C++类,但我希望坚持使用 C 并了解如何以程序语言而不是 哎呀。

如果你想学习C习语和约定,那么一定要这样做。 你所描述的不是一个。 C 代码和库绝对可以使用 OO 原则(如封装)进行设计,在某种程度上甚至可以使用多态性,但它不是通过您描述的机制传统实现的。 这个答案涉及用于此目的的一些方法。

这是一个好的做法吗?

TLDR;没有。

背景:

在过去的一年半里,我几乎只在STM32微控制器上使用嵌入式C进行编程(而不是使用C++或"C+",我将在下面描述)。对我来说,像我一样在架构层面学习 C 是非常有见地的。我非常努力地学习了C架构,以至于我可以说我"了解C"。事实证明,众所周知,C 和 C++ 不是同一种语言。在语法层面,C 几乎完全是C++的一个子集(有一些关键差异,C 支持C++不支持的东西),这就是为什么人们(包括我自己之前)经常认为/认为它们几乎是同一种语言,但在架构层面上它们是截然不同的动物。


旁白:

请注意,我最喜欢的嵌入式方法是使用一些俗称的"C+"。它基本上是使用C++编译器来编写C风格的嵌入式代码。你基本上只是按照你期望的方式写C,除了你使用C++类来大大简化(否则纯C)架构。换句话说,"C+"是一个假名,用于描述使用C++编译器编写类似C的代码,该代码使用类而不是"基于对象的C"体系结构(如下所述)。有时也可以使用一些高级C++概念,例如运算符重载或模板,但在大多数情况下避免使用 STL,以免在初始化后意外使用动态分配(幕后和自动,例如C++向量),因为在正常运行时动态内存分配/释放会快速耗尽稀缺的 RAM 资源,并使确定性代码不确定.所谓的"C+"还可能包括混合使用C(使用C编译器编译)和C++(使用C++编译器编译),根据需要链接在一起(不要忘记C++代码中包含的C头文件中的extern "C"用法)。

核心Arduino源代码(同样,核心,不一定是他们的示例"草图"或初学者的示例代码)做得很好,可以用作良好的"C +"设计的模型。 <== 在你攻击我之前,请像我一样研究 Arduino 源代码十几个小时[再次,不是示例"草图", 但他们的实际源代码,链接到下面],并立即放弃您的"Arduino 适合初学者"的骄傲。

  • AVR核心(C和"C+"风格的C++的混合)在这里:https://github.com/arduino/ArduinoCore-avr/tree/master/cores/arduino
  • 一些核心库("C+"风格的C++)在这里: https://github.com/arduino/ArduinoCore-avr/tree/master/libraries

[旁]


架构 C 注释:

因此,关于C架构(即:实际的C,而不是"C +"/C风格的C++):

如您所知,C 不是 OO 语言,但它可以用"基于对象"的风格编写。请注意,我说的是">基于对象的",而不是"面向对象",因为我听到其他迂腐的C程序员就是这样说的。我可以说我写了基于对象的C架构,它实际上很有趣。

要创建基于对象的 C 体系结构,请记住以下几点:

  1. 命名空间可以在 C 中完成,只需在命名空间名称前面加上下划线即可。毕竟,这就是命名空间的全部内容。例如:mylibraryname_foo()mylibraryname_bar()等例如,将其应用于枚举,因为 C 没有像 C++ 这样的"枚举类"。也将其应用于所有C类"方法",因为C没有类。应用于与特定库相关的所有全局变量或定义。
  2. 在制作 C "类"时,您有 2 个主要的架构选项,这两个选项都非常有效且被广泛使用:
    1. 使用公共结构(可能隐藏在名为"myheader_private.h"的标头中,以赋予它们伪隐私感)
    2. 使用不透明结构(通常称为"不透明指针",因为它们是指向不透明结构的指针)
  3. 在制作 C "类"时,您可以选择将指针包装到上述结构内部的函数,以使其具有更"C++"类型的感觉。这有点常见,但在我看来是一个可怕的想法,它使代码几乎不可能遵循,并且很难阅读,理解和维护。

第一个选项,公共结构:

创建一个带有结构定义的头文件,其中包含所有"类数据"。我建议您不要包含指向函数的指针(稍后将讨论)。这实质上相当于"所有成员都是公共的C++类"。缺点是你不会隐藏数据。好处是您可以使用所有 C"类对象"的静态内存分配,因为包含这些库标头的用户代码知道结构的完整规范和大小。

第二个选项:不透明结构:

在库头文件中,对结构进行前向声明:

/// Opaque pointer (handle) to C-style "object" of "class" type mylibrarymodule:
typedef struct mylibrarymodule_s *mylibrarymodule_h;

在库 .c 源文件中,提供struct mylibrarymodule_s的完整定义。由于此库的用户仅包含头文件,因此他们无法看到此不透明结构的完整实现或大小。这就是"不透明"的意思:"隐藏"。它被混淆或隐藏起来。这实质上相当于"所有成员都是私有的C++类"。好处是您可以隐藏真正的数据。缺点是你不能使用这个库对用户代码中的任何 C"类对象"使用静态内存分配,因为包括这个库在内的任何用户代码甚至不知道结构有多大,所以它不能静态分配。相反,库必须在程序初始化时执行一次动态内存分配,即使对于嵌入式确定性实时安全关键系统也是安全的,因为在正常程序执行期间不会分配或释放内存。

有关选项 2 的详细和完整示例(不要混淆:我在链接到此处的答案中称其为"选项 1.5"),请参阅我对不透明结构/指针的其他答案:不透明 C 结构:应该如何声明它们?.

就个人而言,我认为具有静态内存分配和"所有公共成员"的选项 1 可能是我的首选方法,但我最熟悉不透明结构选项 2 方法,因为这是我最常用的 C 代码库。

上面的项目符号 3:包括指向结构中函数的指针。

这是可以做到的,有些人做到了,但我真的很讨厌它。别这样。它只会让你的代码如此臭,难以理解。例如,在Eclipse中,它有一个出色的索引器,我可以按Ctrl +单击任何内容,它将跳转到其定义。如果我想查看我在 C"对象"上调用的函数的实现怎么办?我按 Ctrl + 单击它,它会跳转到指向函数的指针的声明。但是功能在哪里呢???我不知道!我可能需要 10 分钟grepping 并使用查找或搜索工具,在代码库周围挖掘,以找到臭函数定义。一旦找到它,我就会忘记我在哪里,每次我使用这种方法编辑库模块时,我都必须为每个函数重复一遍。这很糟糕。上面的不透明指针方法效果很好,公共指针方法也很容易。

现在,直接回答您的问题:

为了使库使用者更容易对这些结构实例进行常见操作,是否可以在结构本身中提供指向此类函数的函数指针?

是的,你可以,但它只会让打电话更容易。别这样。找到函数来查看其实现变得非常困难。

这是一种好的做法吗?

不,改用上面的选项 1 或选项 2,您现在只需在每个 C"对象"上调用 C "命名空间"方法"。您只需将"C 类的成员"作为每次调用的第一个参数传递到函数中即可。这意味着您可以在C++

中执行以下操作:
myclass.dosomething(int a, int b);

你只需要在基于对象的C中做:

// Notice that you must pass the "guts", or member data
// (`mylibrarymodule` here), of each C "class" into the namespaced
// "methods" to operate on said C "class object"!
// - Essentially you're passing around the guts (member variables)
//  of the C "class" (which guts are frequently referred to as
// "private data", or just `priv` in C lingo) to each function that
// needs to operate on a C object
mylibrarymodule_dosomething(mylibrarymodule_h mylibrarymodule, int a, int b); 

多线程方面是否存在问题,其中实用程序函数与不同的参数并行调用等等?

是的,与多个线程尝试访问相同数据的任何多线程情况相同。只需向每个基于 C 结构的"对象"添加一个互斥锁,并确保作用于您的 C"对象"的每个"方法"在对 C"对象"的任何共享易失性成员进行操作之前,根据需要正确锁定(获取)和解锁(提供)互斥锁。

相关:

  1. 不透明的 C 结构:应该如何声明它们?[使用"基于对象"的 C 体系结构]

我想建议你阅读com规范,你会收获很多。 所有这些 COM、OLE 和 DCOM 技术都基于一个简单的结构,其中包含自己的数据和方法。

https://www.scribd.com/document/45643943/Com-Spec

在这里简化更多内容 http://www.voidcn.com/article/p-fixbymia-beu.html

相关内容

最新更新