typedef指针是一个好主意吗?

96

我查看了一些代码,发现惯例是将指针类型转换为

SomeStruct* 

进入

typedef SomeStruct* pSomeStruct;

这有任何优点吗?


1
在 C 语言中很常见,因为你试图隐藏它是一个指针的事实。这是你传递给库中所有接口的对象。在 C++ 中这不常见,尽管并非没有听说过。 - Martin York
4
关于这个主题的一篇有趣文章:谈话:仲夏之夜的疯狂。另外,参考Linux内核编码风格,其中极端看法是“不要为指针定义typedef(也不要为结构体或联合体提供typedefs)”。 - Jonathan Leffler
@MartinYork 可以补充一下,在C++中不需要这样做,因为它支持按引用传递。 - klutt
@klutt 在C++中,你总是使用方法而不是函数,所以隐藏的对象(this)始终存在。 - Martin York
应用程序匈牙利命名法 vs 系统匈牙利命名法。 - Gerhard
15个回答

121

当指针本身可以被视为“黑匣子”即一个数据片段,其内部表示与代码无关时,这种方法是适当的。

如果您的代码从不对指针进行解引用,并且您只是通过API函数传递它(有时通过引用),那么typedef不仅可以减少代码中* 的数量,而且还建议程序员不应该真的去管指针。

这也使得将来更容易更改API,如果需要的话。例如,如果您更改使用ID而不是指针(反之亦然),现有代码不会中断,因为指针本来就不应该被解引用。


11
现实世界的例子:在 OS X 中,CoreFoundation 框架广泛使用这种技术,并将其称为“不透明”数据类型。 - hbw
4
同样适用于OS X操作系统:pthread_t是一个不透明类型,它被typedef为指向结构体'_opaque_pthread_t'的指针,该结构体本身是一个不透明的字节数组。 - Adam Rosenfield
13
反例:FILE *fp?实际上,我同意你的观点,但有充分的先例相反。 - Jonathan Leffler
12
当然,如果你这样做,你就不要把不透明类型命名为“pFoo”。 - MSalters
3
FILE* 没有被 typedef 为模糊指针的原因是为了允许像 getc()putc() 这样的宏直接操作 FILE 结构体,以在大多数情况下避免函数调用开销。 - chqrlie
显示剩余7条评论

83

根据我的经验,隐藏 '*' 会使代码难以阅读。


1
不太清楚。我的意思是,如果你有一个指向 Device 的指针,并将其 typedefDevice_Ptr(而不是使用 Device * 或类似的方式),那么它就非常易读。我看到很多成熟且相当大的库都这样做。特别是在 C++ 中,如果你添加了模板,这可以减少你的打字量。 - rbaleksandar
@rbaleksandar 再次强调,如果Device*是一个不透明的句柄也没关系。 - Alex D
1
作为一个 C 语言新手,我认为 C 的指针语法是一种可憎的东西 - 只有那些不幸花了十年以上时间学会喜欢它的人才会喜欢它。我宁愿选择 myTypePtrmyTypeRef 而不是那个星号。:P - Christoffer Bubach

29

我只有在处理函数指针时才在typedef内部使用指针:

typedef void (*SigCatcher(int, void (*)(int)))(int);

typedef void (*SigCatcher)(int);

SigCatcher old = signal(SIGINT, SIG_IGN);
否则,我认为它们比有用更令人困惑。
被划掉的声明是指向 signal() 函数的指针的正确类型,而不是信号捕获器的类型。可以通过使用上面纠正后的SigCatcher类型来使其更清晰。

 typedef SigCatcher (*SignalFunction)(int, SigCatcher);

或者,声明signal()函数:

 extern SigCatcher signal(int, SigCatcher);
那么,一个"SignalFunction"是指向一个函数的指针,该函数接受两个参数(一个"int"和一个"SigCatcher"),并返回一个"SigCatcher"。而"signal()"本身也是一个函数,它接受两个参数(一个"int"和一个"SigCatcher"),并返回一个"SigCatcher"。

这个少标点符号的typedef能正常工作吗? “typedef void SigCatcher(int,void (*)(int))(int)” - sigjuice
不行;gcc 报错:“error: SigCatcher 声明为返回函数的函数”。 - Jonathan Leffler
我刚刚发现了一个更简单的typedef声明signal():http://www.freebsd.org/cgi/man.cgi?query=signal - sigjuice
@Sigjuice:即使我没有去BSD页面(直到我编辑完材料后才去),我也看到了我的答案存在巨大问题,现在已经修复了。而BSD所称的sig_t与我的SigCatcher相匹配,是的,它极大地简化了signal()的声明。 - Jonathan Leffler
2
很酷。在我的看法中,使用显式*的等效声明并不难看。typedef void SigCatcher(int); extern SigCatcher *signal(int, SigCatcher *); - sigjuice

19

这可以帮助你避免一些错误。例如在以下代码中:

int* pointer1, pointer2;

pointer2不是指向int *的指针,而只是一个简单的int

但是,使用typedef就不会发生这种情况:

typedef int* pInt;
pInt pointer1, pointer2;

它们现在都是int *


18
如果您将星号放在指针1上而不是放在"int"上,则声明会更加合理,这样指针2就不是指针,只是一个整数。 - dreamlax
3
我认为C程序员会认为在声明指针变量时使用解引用运算符更多地是一种习惯用法。例如,在K&R中,他们说:“'int *ip'旨在作为助记符;它表示表达式'*ip'是一个整数。” - hbw
3
但是每个人都知道这种语法,而且最好一次只声明和初始化一个变量...我很少写int *pointer1, *pointer2;因为在下一行你有两个未定义值的指针。 - Daniel Daranas
2
Roalt 的意思是说:“int *pointer1” 而不是 “int* pointer1”。 - Artelius
2
@Daniel Daranas: int *p1 = NULL, *p2 = NULL; -> @Daniel Daranas:int *p1 = NULL,*p2 = NULL; - Matt Joiner
显示剩余4条评论

16

我的回答是明确的“不”。

为什么呢?

首先,你只是把一个单字符*换成另一个单字符p。这没有任何收益。仅凭这一点,就应该让你避免这样做,因为做无用的额外工作总是不好的。

其次,这是重要原因,*携带的含义是不好隐藏的。如果我像这样将某些东西传递给一个函数

void foo(SomeType bar);

void baz() {
    SomeType myBar = getSomeType();
    foo(myBar);
}

我不希望通过将myBar传递给foo()来改变它的含义。毕竟,我是按值传递的,所以foo()只会看到myBar的副本,对吧?但是当SomeType被别名指向某种指针时就不是这样了!特别是当该指针作为指向某种对象的引用时,其值基本上就是指针的含义:我不关心指针本身是否更改(由于按值传递),我只关心对象是否更改(指针背后的对象)。

这同样适用于C指针和C++智能指针:如果您向用户隐藏它们是指针的事实,您将创建完全不必要的混乱。所以,请不要给您的指针取别名。

(我认为typedef指针类型的习惯只是试图掩盖程序员拥有多少个星号的误导性尝试http://wiki.c2.com/?ThreeStarProgrammer。)


另一个原因是,void *vp=/*...*/; foo(vp);foo(0); 将没有类型保护。(用于枚举类型的typedef和用于永远不应该显示为数字的数值typedef也有类似的问题)。 - Petr Skocik
1
即使将指针传递给函数,指针myBar本身仍然会被复制,即使它所指向的数据没有被复制。因此,myBar的值(一个地址)仍然不能被foo更改,但是myBar所指向的值可以被更改。 - Aposhian
@Aposhian 是的。那又怎样? - 我的观点是指针通常作为内存中某个对象的代理。指针的语义基本上就是其背后的对象的语义,而这些语义可以通过接收指针的任何函数来更改。例如,如果您有一个类型为struct _fraction并将其指针typedef为Fraction,则语句Fraction a = fraction(84, 2); printFrac(a); doSomething(a); printFrac(a);可能会产生混乱的输出。使用Fraction* a = fraction(84, 2);,就清楚了doSomething(a)可能通过更改对象来改变a的含义。 - cmaster - reinstate monica
是的,你说得对。我同意typedef指针通常是一种混淆。但我不希望任何人将指针的含义/语义和它的值混为一谈,这两者是分开且独立的。记住这个区别在重新分配指针变量或使用指向指针的指针时非常重要。 - Aposhian
1
@Aposhian 啊,我现在明白了。好的,我在那个问题点加了一些词,希望能让我的意思更清楚。谢谢你指出这一点。 - cmaster - reinstate monica

8
Assuming the language of interest is C, this discussion does not take into account any implications for C++.

使用指针typedef来定义没有标签的结构体

问题“作为指针定义的结构体大小”提出了一个有趣的侧面问题,即使用typedef来定义(结构)指针。

考虑无标签具体(而非不透明)结构类型定义:

typedef struct { int field1; double field2; } *Information;

成员的细节与本讨论完全无关,重要的是这不是一个不透明类型,比如typedef struct tag *tag;(而且你不能在没有标签的情况下通过typedef定义此类不透明类型)。
提出的问题是“如何找到该结构的大小”?
简短的答案是“只能通过类型的变量”。没有标签可用于sizeof(struct tag)。例如,你不能有用地编写sizeof(*Information)sizeof(Information *)是指向指针类型的指针大小,而不是结构类型的大小。
实际上,如果你想分配这样的结构,除了通过动态分配(或模拟动态分配的代理技术),你不能创建一个结构。没有办法创建名为Information的结构类型的局部变量,也没有办法创建结构类型的文件范围(全局或static)变量,也没有办法将这样的结构(而不是指向这样的结构的指针)嵌入另一个结构或联合类型中。
你必须编写:sizeof(*information)
Information info = malloc(sizeof(*info));

除了指针在 typedef 中被隐藏的事实,这是一个好的做法——如果 info 的类型发生变化,大小分配将保持准确。但在这种情况下,这也是获取结构体大小和分配结构体的唯一方法。没有其他方式创建结构体的实例。

这会有害吗?

这取决于你的目标。

这不是不透明类型——当指针类型进行 typedef 时,必须定义结构体的详细信息。

这是一种只能与动态内存分配一起使用的类型。

这是一种无名字的类型。结构体类型的指针有一个名称,但结构体类型本身没有。

如果你想强制执行动态分配,这似乎是一种方法。

总的来说,这更可能会引起混乱和焦虑,而不是启示。

摘要

通常,使用 typedef 定义指向无标记结构体类型的指针是一个不好的做法。


2
实际上,您可以通过将“取消引用”的NULL强制转换为Information并应用sizeof来获取大小。尝试使用sizeof(*(Infomation)0) - tstanisl

5

这是一个风格问题。你会发现在Windows的头文件中经常出现这种代码。虽然他们更喜欢全大写版本而不是以小写p为前缀。

个人而言,我避免使用typedef。让用户明确地表示他们想要一个Foo*比PFoo更清晰。typedef现在最适合用于使STL可读 :)

typedef stl::map<stl::wstring,CAdapt<CComPtr<IFoo>> NameToFooMap;

5

这个问题(像许多答案一样)取决于具体情况。

在C语言中,这种情况非常普遍,因为你试图掩盖一个对象是指针的事实。你试图暗示这是所有函数操作的对象(我们知道它在底层是一个指针,但它代表你正在操作的对象)。

MYDB   db = MYDBcreateDB("Plop://djdjdjjdjd");

MYDBDoSomthingWithDB(db,5,6,7);
CallLocalFuc(db); // if db is not a pointer things could be complicated.
MYDBdestroyDB(db);

MYDB下面可能有指向某个对象的指针。

在C++中,这不再是必需的。
主要是因为我们可以通过引用传递东西,并将方法并入类声明中。

MyDB   db("Plop://djdjdjjdjd");

db.DoSomthingWithDB(5,6,7);
CallLocalFuc(db);   // This time we can call be reference.
db.destroyDB();     // Or let the destructor handle it.

5

1
你能否添加一下你提到的文章链接?另外,他是个男士! :-) - Fabio says Reinstate Monica
1
@Azat Ibrakov 这只是一个小问题,但是虽然 Herb 明确是男性,但他所写的 Guru 却是女性。因此,在使用代词时以女性称呼她是合适的。我知道她实际上是 Herb 的另一个自我形象,但如果你阅读了整个系列,包括前20篇文章中讲述的故事,她的身份就很有道理了。 - dgnuff

4

如果这样做,您将无法创建const pSomeStruct的STL容器,因为编译器会读取:

list<const pSomeStruct> structs;

as

list<SomeStruct * const> structs;

这不是一个合法的STL容器,因为元素不可分配。

请参阅此问题


当然可以,但是人们永远不会写list<const ordinary_type>,因为它无法工作,所以也不会有任何混淆。 - user267885
1
我不确定你所说的“ordinary_type”是什么意思。list<const SomeStruct*>是完全有效的,因为const SomeStruct*是可复制赋值的。 - Dan Hook

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