typedef一个指针类型被认为是一种不好的做法吗?

16

可能是重复问题:
typedef指针好主意吗?

我在使用许多API时都看到了这种奇怪的用法:

typedef type_t *TYPE;

我的观点是声明一个类型为 TYPE 的变量并不会清楚地表明实际上是声明了一个指针。

你是否跟我一样认为这会带来很多混淆?这是为了强制封装,还是有其他原因?你认为这是一种不好的实践吗?


1
“Typedef指针是一个好主意吗?”的答案中,只有一个提到与const无用的交互 - 而且这个答案排名较低。在我看来,const问题是这些typedef的最大问题,因此并不是重复的问题。 - John Marshall
@John:我不同意你的结论。你指出一个问题被问了很久后添加的答案没有得到太多关注,但问题本质上仍然是相同的。 - dmckee --- ex-moderator kitten
@dmckee:好吧,我想我的真正观点是“不应该立即将其关闭为重复项,因为它的答案提供了有趣的新信息”。而且,实际上,我的观点是Blagovest应该再等一会儿,这样就可以接受_我的_答案 :-) - John Marshall
@John Marshall:我现在接受了你的答案,因为我不得不睡觉 :-) - Blagovest Buyukliev
你在Windows API中会看到很多这样的东西。我绝对讨厌它。它隐藏了类型,导致更多的混淆。我更喜欢int* i而不是PINT = 我想Windows的BASIC遗产使他们试图隐藏指针。其次,全大写看起来很可怕。我实际上从未费心去使用win32 api,因为它太乱了。 - W.K.S
8个回答

24

一般来说,这是一个不好的做法。主要问题在于它与 const 不兼容:

typedef type_t *TYPE;
extern void set_type(TYPE t);

void foo(const TYPE mytype) {
  set_type(mytype);  // Error expected, but in fact compiles
}

为了让foo()的作者表达他们真正的意思,提供TYPE的库也必须提供CONST_TYPE
typedef const type_t *CONST_TYPE;

因此,一个经验法则:

对于结构体(特别是不完整的结构体),应该创建typedefs,而不是指向这些结构体的指针。

如果底层结构的定义不希望公开(通常是值得赞扬的),那么封装应该由结构体不完整来提供,而不是通过不方便的typedefs:

这样 foo() 可以拥有签名 void foo(CONST_TYPE mytype),到这一步我们已经陷入了荒谬。

struct type_t;
typedef struct type_t type_t;

void set_type(type_t *);
int get_type_field(const type_t *);

有趣的是,某些类型似乎不需要 const 版本。例如,我从来没有想过要一个 const_pthread_t,可以在其上调用 pthread_getschedparam 但不能调用 setschedparam。当然,const pthread_t 无法达到这个目的,就像您的示例中 typedef 的指针无法实现一样。而 ftell 接受 FILE* 而不是 const FILE*,虽然我认为它应该是 const 的,以支持对 FILE* 的 const 安全使用。因此,我认为在某些情况下,这个 const 问题很重要/关键,但并非总是如此。这是 C 的预先 const 遗留问题,也许是懒惰 :-) - Steve Jessop
确实。我查看了标准是否已经使用const FILE*进行了任何更新,但是...并没有。 (我还希望能帮助您理解FILE*!:-))也许这与某些东西感觉像原子类型有关--人们也不会写const int。或者是一种鸡生蛋蛋生鸡的问题:如果没有任何查询函数使用const FILE*,那么为什么要编写自己的foo()函数来使用它--您将如何使用参数? - John Marshall
1
ftell不能接受const FILE *,因为(在POSIX或任何其他实现中有线程的情况下),它必须在FILE对象上获取互斥锁,除非可以使用单个原子操作获取当前位置。const FILE *的整个概念基本上是无意义的;它不能用于任何事情。 - R.. GitHub STOP HELPING ICE
1
+1 表示“陷入了荒谬境地”。 - olovb
这个不完整结构体的例子(而不是指向结构体的指针)对于封装非常重要 - 我看到的许多例子都在指针上使用了typedef,这会导致问题,对于不习惯这种用法的程序员来说很难调试。然而,了解使用不完整结构体而不是typedef指针的缺点也是有用的。 - davidA

6

一种常见的习惯用法是在类型后缀加上_p,以指示它是一个指针,同时保留指针特性。

有时候必须仅使用指针类型,如果指向的结构体不是公开可用的。这有助于实现数据隐藏。例如:

typedef struct hidden_secret_object * object;
void change_object(object foo);

这可以让你改变hidden_secret_object的结构,而不会破坏外部代码。

3
为什么在数据隐藏的typedef中需要指针?typedef struct h_s_o object; void change_object(object *foo);这样做可以实现同样的效果。这个例子与问题看起来不相关。 - schot
有时候一个类型是打算不透明的,而在不同的系统上可能会使用不同的类型。在一些系统上,这种类型可能会很大,所以通过指针传递更有效率,而在另一些系统上它可能只是作为整数传递的标记。 - nategoose

5

我也觉得不够清晰。同样,我也不喜欢全大写的类型(我尝试将其保留给 #define)。

这种方式会让人误以为它实际上是值类型,而我们正在谈论指针类型。指针类型可以完全用智能指针来抽象掉,但在C语言中并不常见。

像之前提到的一样,在末尾加上 _p、_ptr、Pointer 或任何类似的内容可以增加清晰度;虽然会增加输入量,但可以防止你因为低级错误(例如使用 '.' 而不是 '->')浪费宝贵的开发时间。


5
这取决于您想要实现什么目标。根据您的陈述,没有有意义的“是或否”答案。
- 如果您正在尝试创建一种抽象句柄类型,暗示用户不应知道或关心该类型后面隐藏了什么,那么将指针类型进行typedef是完全可以接受的。重点在于今天它可能是指针类型,明天可能会变成整数类型,以后可能会变成其他类型。这正是大多数库接口中通常使用指针类型typedef的用途。 - 您说有时“不清楚是否声明了指针”。但在这种用法模型下,这正是重点!它应该是“不清楚的”。事实上,该类型恰好是混淆的指针类型。这是您不需要知道并且不应该依赖的东西。 - 这种用法模型的一个经典例子是标准库中的va_list类型。在某些实现中,它很容易成为指针类型的typedef。但这是您不应该知道或依赖的。 - 另一个例子是Windows API中HWND类型的定义。它也是指针类型的typedef,但这与您无关。
- 完全不同的情况是,当您将指针类型typedef为一种简写形式时,只是为了使声明更短,以免每次都要键入*字符。在这种情况下,typedef始终表示指针类型的事实暴露给用户。通常,这种用法不是良好的编程实践。如果用户想要创建别名以避免每次键入*,他们可以自己做到。
- 这种用法模型通常会导致更加混淆的代码,正如您在原帖中提到的那样。 - 在Windows API中也可以找到typedef的这种错误用法示例。typedef名称(例如PINT)正是这种缺陷的用法模型。

+1 无法更好地表达了 - R.. GitHub STOP HELPING ICE

1

有时候它会咬我一口:

for (vector<typedef_name_that_doesnt_indicate_pointerness_at_all>::iterator it;
    it != v.end(); ++it)
{
   it->foo(); // should have been written (*it)->foo();
}

唯一可接受使用typedef的情况是,如果类型确实需要是完全不透明的,并且根本不被直接访问。也就是说,如果某人需要在API之外对其进行解引用,则指针性质不应该隐藏在typedef后面。

1

如果它是指向不完整类型的指针,或者由于其他原因用户不需要对其进行解引用,我认为这并不是不良实践。我从来没有理解过FILE*

如果您正在进行多级间接引用,并且希望在某些情况下使用它而忽略其中一些级别,则我也认为这不是不良实践。typedef char **argarray之类的东西。

如果用户需要对其进行解引用,则在C中,保留*可能是最好的选择。在C++中,人们习惯于具有重载operator*的用户定义类型,例如迭代器。在C中,这并不正常。


1

存储类限定符(如'const')在typedef指针和“自然”指针中的工作方式不同。虽然这对于'const'通常不是一件好事,但对于编译器特定的存储类(如“xdata”)非常有用。例如:

xdata WOKKA *foo;

将声明“foo”为指针,存储在默认存储类中,在xdata中指向WOKKA。而声明:

xdata WOKKA_PTR bar;

将声明“bar”为指针,存储在xdata中,指向WOKKA_PTR中指定的任何存储类中的WOKKA。如果库例程期望指向具有特定存储类的内容的指针,则在指针类型中定义这些存储类可能会很有用。


0
也许让它更具体的一种方法是将新指针类型称为type_ptr或类似的名称:
 typedef type_t* type_ptr;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接