12

有时,在 C 中,您会这样做:

typedef struct foo {
   unsigned int some_data;
} foo; /* btw, foo_t is discouraged */

要以 OO 的方式使用这种新类型,您可能有如下的分配/释放对:

foo *foo_alloc(/* various "constructor" params */);
void foo_free(foo *bar);

或者,或者初始化/清除对(可能返回错误代码):

int foo_init(foo *bar, /* and various "constructor" params */);
int foo_clear(foo *bar);

我已经看到使用以下成语,特别是在 MPFR 库中:

struct foo {
   unsigned int some_data;
};
typedef struct foo foo[1]; /* <- notice, 1-element array */
typedef struct foo *foo_ptr; /* let's create a ptr-type */

alloc/free 和 init/clear 对现在读取:

foo_ptr foo_alloc(/* various "constructor" params */);
void foo_free(foo_ptr bar);
int foo_init(foo_ptr bar, /* and various "constructor" params */);
int foo_clear(foo_ptr bar);

现在您可以像这样使用它(例如,init/clear 对):

int main()
{  
   foo bar; /* constructed but NOT initialized yet */
   foo_init(bar); /* initialize bar object, alloc stuff on heap, etc. */
   /* use bar */
   foo_clear(bar); /* clear bar object, free stuff on heap, etc. */
}

备注: init/clear 对似乎允许使用更通用的方式来初始化和清除对象。与 alloc/free 对相比,init/clear 对要求已经构造了一个“浅”对象。“深度”构造是使用 init 完成的。

问题:1 元素数组“type-idiom”是否有任何不明显的缺陷?

4

2 回答 2

15

这是非常聪明的(但见下文)。

它鼓励了 C 函数参数可以通过引用传递的误导性想法。

如果我在 C 程序中看到这个:

foo bar;
foo_init(bar);

我知道调用foo_init不会修改bar. 我也知道代码在bar未初始化函数时将值传递给函数,这很可能是未定义的行为。

除非我碰巧知道这foo是数组类型的 typedef。然后我突然意识到foo_init(bar)不是传递 的bar而是传递它的第一个元素的地址。现在,每次我看到引用 typefoo或 type 对象的东西时foo,我都必须考虑如何foo将 typedef 定义为单元素数组,然后才能理解代码。

这是一种尝试让 C 看起来像它不是的东西,不像这样的东西:

#define BEGIN {
#define END }

等等。而且它不会导致代码更容易理解,因为它使用了 C 不直接支持的特性。它导致代码更难理解(尤其是对于熟悉 C 的读者而言),因为您必须同时理解定制的声明使整个事情工作的底层 C 语义。

如果你想传递指针,只需传递指针,并明确地进行。参见,例如,在FILE*定义的各种标准函数中的使用<stdio.h>。没有尝试将指针隐藏在宏或 typedef 后面,C 程序员几十年来一直在使用该接口。

如果您想编写看起来像通过引用传递参数的代码,请定义一些类似函数的宏,并给它们全大写的名称,以便知识渊博的读者知道发生了一些奇怪的事情。

我在上面说过,这很“聪明”。我想起了我第一次学习 C 语言时所做的一件事:

#define EVER ;;

这让我写了一个无限循环:

for (EVER) {
    /* ... */
}

当时,我觉得这很聪明。

我仍然认为它很聪明。我只是不再认为这是一件好事。

于 2013-09-01T20:08:31.443 回答
3

这种方法的唯一优点是代码看起来更漂亮,打字更容易。它允许用户在没有动态分配的情况下在堆栈上创建结构,如下所示:

foo bar;

但是,该结构仍然可以传递给需要指针类型的函数,而无需用户&bar每次都转换为指针。

foo_init(bar);

如果没有 1 元素数组,则需要您提到的 alloc 函数或常量&使用。

foo_init(&bar);

我能想到的唯一陷阱是与直接堆栈分配相关的正常问题。如果这在其他代码使用的库中,则对结构的更新可能会在将来破坏客户端代码,这在使用 alloc free 对时不会发生。

于 2013-09-01T16:58:01.460 回答