typedef 指针是个好主意吗?

Posted

技术标签:

【中文标题】typedef 指针是个好主意吗?【英文标题】:Is it a good idea to typedef pointers? 【发布时间】:2010-10-19 12:11:56 【问题描述】:

我查看了一些代码并注意到约定是将指针类型转换为

SomeStruct* 

进入

typedef SomeStruct* pSomeStruct;

这样做有什么好处吗?

【问题讨论】:

它在 C 中很常见。因为你试图隐藏它是一个指针的事实。这是您传递给库中所有接口的对象。在 C++ 中,虽然并非闻所未闻,但它并不常见且不受欢迎。 一篇关于这个一般主题的有趣文章:Conversations: A Midsummer's Night Madness 另请参阅Linux Kernel Coding Style 以了解“不要 typedef 指针(并且不要为结构或联合提供 typedef)”的极端观点。 @MartinYork 有人可能会补充说,在 C++ 中不需要它,因为它支持按引用传递 @klutt 在 C++ 中你使用方法而不是函数,所以隐藏对象(this)总是存在的。 【参考方案1】:

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

本质上,如果您的代码从不取消引用指针,而您只是将它传递给 API 函数(有时通过引用),那么 typedef 不仅会减少 *s 的数量您的代码,但也建议程序员不要真正干预指针。

这也使得将来如果需要更改 API 变得更容易。例如,如果您更改为使用 ID 而不是指针(反之亦然),现有代码不会中断,因为从一开始就不应该取消引用指针。

【讨论】:

真实世界的例子:在 OS X 中,CoreFoundation 框架广泛使用了这种技术,将它们称为“不透明”数据类型。 同样在 OS X 中:pthread_t 是不透明类型;它被定义为指向“struct _opaque_pthread_t”的指针,它本身就是一个不透明的字节数组。 反例:FILE *fp?事实上,我同意你的观点,但相反的先例很多。 '当然,如果你这样做,你不会将不透明类型命名为“pFoo”。 @MattJoiner:FILE* 没有被类型定义为不透明指针的原因是允许 getc()putc() 等宏直接操作 FILE 结构并避免函数调用开销在大多数情况下。【参考方案2】:

根据我的经验不是。隐藏“*”会使代码难以阅读。

【讨论】:

不知道。我的意思是,如果你有一个指针,让我们说Device,而你将typedef 指向Device_Ptr(而不是必须使用Device *)或类似的,那么它是非常易读的。我看到很多成熟且相当大的库都可以做到这一点。尤其是在 C++ 中,如果您添加模板,这可以节省您的手指打字时间。 @rbaleksandar 如果 Device* 是一个不透明的句柄,那也没关系。 作为一个 C n00b,我认为 C 的指针语法令人憎恶——只有那些已经花了 10 年以上的时间才学会喜欢它的不幸灵魂才喜欢它。任何一天我都会选择myTypePtrmyTypeRef 超过那颗星。 :P【参考方案3】:

我唯一一次在 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 是一个指向函数的指针,该函数接受两个参数(intSigCatcher)并返回 SigCatcher。而signal() 本身就是一个函数,它接受两个参数(一个int 和一个SigCatcher)并返回一个SigCatcher

【讨论】:

这种标点符号较少的 typedef 是否有效? "typedef void SigCatcher(int, void (*)(int))(int)" 否; gcc 说“错误:SigCatcher 声明为函数返回函数”。 我刚刚遇到了更简单的信号()freebsd.org/cgi/man.cgi?query=signal987654321@ @Sigjuice:即使没有转到 BSD 页面(我在编辑材料之后才这样做),我发现我的答案存在很大问题,现在已修复。 BSD 所称的sig_t 与我的SigCatcher 匹配,是的,它极大地简化了signal() 的声明。 酷。恕我直言,带有显式 * 的等效声明看起来还不错。 typedef void SigCatcher(int); extern SigCatcher *signal(int, SigCatcher *);【参考方案4】:

这可以帮助您避免一些错误。比如下面的代码:

int* pointer1, pointer2;

pointer2 不是 int *,它是简单的 int。 但是对于 typedef,这不会发生:

typedef int* pInt;
pInt pointer1, pointer2;

它们现在都是 int *

【讨论】:

如果你把 * 触摸指针 1 而不是触摸“int”,声明更有意义,指针 2 不是指针,只是一个 int。 我认为 C 程序员会争辩说,在声明指针变量时使用解引用运算符更像是一种惯用的东西。为了检查,在 K&R 中,他们说:“'int *ip' 旨在作为助记符;它表示表达式 '*ip' 是一个 int。” 但是每个人都知道这个语法,而且最好还是一次声明和初始化一个变量……我很少写 int *pointer1, *pointer2;因为在下一行中,您有两个未定义值的漂亮指针。 Roalt 的意思是:'int *pointer1' 而不是 'int* pointer1'。 @Daniel Daranas: int *p1 = NULL, *p2 = NULL;【参考方案5】:

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

为什么?

嗯,首先,您只需将单个字符 * 交换为另一个单个字符 p。那是增益。仅此一项就可以防止您这样做,因为做多余的无意义的事情总是不好的。

其次,这也是很重要的原因,*带有不好隐藏的意思。如果我将某些东西传递给这样的函数

void foo(SomeType bar);

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

我不希望通过将myBar 传递给foo() 来改变它的含义。毕竟,我是按价值传递的,所以foo() 只会看到myBar 的副本,对吗?当SomeType 被别名为某种指针时不是!特别是当该指针充当对某种对象的引用时,该对象的值基本上就是指针的含义:我不在乎指针本身不会改变(由于按值传递),我对对象是否改变(指针后面的对象)。

这适用于 C 指针和 C++ 智能指针:如果您隐藏它们是指向您的用户的指针这一事实,您将造成完全不必要的混淆。所以,请不要给你的指针起别名。

(我认为定义指针类型的习惯只是一种错误的尝试,试图隐藏一个程序员有多少颗星http://wiki.c2.com/?ThreeStarProgrammer。)

【讨论】:

+1。另一个原因是不会有来自void *vp=/*...*/; foo(vp);foo(0); 的类型系统保护。 (枚举类型定义和数字类型定义用于永远不会显示为数字的事物有类似的问题)。 即使您将 指针 传递给函数,指针myBar 本身仍在被复制,即使它指向的数据不是。所以myBar(地址)的仍然不能被foo改变,但是myBar指向的值可以。 @Aposhian 是的。所以? - 我的观点是指针通常充当内存中某些对象的代理。指针的语义几乎就是它背后对象的语义,任何接收指针的函数都可以更改这些语义。例如,如果您有一个类型 struct _fraction 并将其指针类型定义为 Fraction,则语句 Fraction a = fraction(84, 2); printFrac(a); doSomething(a); printFrac(a); 可能会产生令人困惑的输出。对于Fraction* a = fraction(84, 2);,很明显doSomething(a) 可以通过改变对象来改变a 的含义。 是的,你是对的。我同意 typedef-ing 指针通常是一种混淆。我只是不希望任何阅读本文的人将指针的含义/语义和它的混为一谈,它们是分开且不同的。在重新分配指针变量或使用指向指针的指针时,记住这一区别至关重要。 @Aposhian 啊,现在我明白了。好吧,我在有问题的地方添加了一些词,希望使我的意思更清楚。感谢您指出这一点。【参考方案6】:

这是风格问题。您在 Windows 头文件中经常看到这种代码。尽管他们倾向于使用全大写版本而不是以小写 p 为前缀。

我个人避免使用 typedef。让用户明确表示他们想要 Foo* 比 PFoo 要清楚得多。如今,Typedef 最适合使 STL 可读:)

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

【讨论】:

【参考方案7】:

它(就像许多答案一样)取决于。

在 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.

【讨论】:

【参考方案8】:

假设感兴趣的语言是 C。尚未考虑 C++ 的分支。

对未标记的结构使用指针 typedef

Size of a struct that is defined as a pointer 的问题引发了一个有趣的旁白,即使用 typedef 处理(结构)指针。

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

typedef struct  int field1; double field2;  *Information;

成员的详细信息与本次讨论完全无关;重要的是这不是像 typedef struct tag *tag; 这样的不透明类型(而且你不能通过没有标签的 typedef 定义这种不透明类型)。

提出的问题是“您如何找到该结构的大小”?

简短的回答是“仅通过类型的变量”。没有标签可用于sizeof(struct tag)。例如,您不能有效地编写 sizeof(*Information),而sizeof(Information *) 是指向指针类型的指针的大小,而不是结构类型的大小。

事实上,如果你想分配这样一个结构,你只能通过动态分配(或模仿动态分配的代理技术)来创建一个。无法创建指针称为Information 的结构类型的局部变量,也无法创建结构类型的文件范围(全局或static)变量,也无法创建将这样的结构(而不​​是指向这样的结构的指针)嵌入到另一个结构或联合类型中。

你可以——必须——写:

Information info = malloc(sizeof(*info));

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

这有害吗?

这取决于你的目标。

这不是不透明类型——当指针类型为typedef'd 时,必须定义结构的细节。

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

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

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

但总的来说,它更容易引起困惑和焦虑,而不是启蒙。

总结

一般来说,使用typedef 定义指向无标记结构类型的指针是个坏主意。

【讨论】:

实际上,您可以通过将“取消引用”NULL 转换为 Information 上的 sizeof 来获得没有对象的大小。试试sizeof(*(Infomation)0)【参考方案9】:

没有。

const混在一起的那一刻会让你的生活变得悲惨

typedef foo *fooptr;
const fooptr bar1;
const foo *bar2

bar1bar2 是同一类型吗?

是的,我只是在引用 Herb Sutter 的 Guru。她说了很多实话。 ;)

-- 编辑--

添加引用文章的链接。

http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835

【讨论】:

能否给您提到的文章添加链接?更何况他是个男人! :-) @Azat Ibrakov 这是一个小问题,但是虽然 Herb 绝对是一个男人,但他所写的 Guru 是女性。因此,用女性来称呼她是合适的。是的,我知道她实际上是 Herb 的另一个自我,但如果您阅读整个系列,包括前 20 篇左右文章中讲述的故事,那么做她自己是有道理的。【参考方案10】:

Typedef 用于使代码更具可读性,但是将指针作为 typedef 会增加混乱。最好避免使用 typedef 指针。

【讨论】:

【参考方案11】:

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

list<const pSomeStruct> structs;

作为

list<SomeStruct * const> structs;

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

看到这个 question 。

【讨论】:

当然,但是永远不会写list&lt;const ordinary_type&gt;,因为它无论如何都行不通,所以不会有任何混淆。 我不确定您所说的 ordinary_type 是什么意思。 list&lt;const SomeStruct*&gt; 完全有效,因为 const SomeStruct* 是 CopyAssignable。【参考方案12】:

Win32 API 对几乎所有结构(如果不是全部)都执行此操作

POINT => *LPPOINT
WNDCLASSEX => *LPWNDCLASSEX
RECT => *LPRECT
PRINT_INFO_2 => *LPPRINT_INFO_2

它的一致性很好,但在我看来它并没有增加任何优雅。

【讨论】:

那是因为 Win32 是一个 C API,它在 C 而不是 C++ 中很常见 这个问题被标记为 C 和 C++,你的意思是什么? 应该注意的是,这在 C 中也不是常见的做法,Win API 几乎是唯一一个这样做的知名 C 库。它还使用匈牙利符号。这是一个非常晦涩的库风格,不应该作为规范提出。 这种表示法可以追溯到 30 年前 Windows 的起源,即 Intel 16 位分段架构。 LP 代表 long pointer,简写符号 LPPOINT ptr; 仅此而已,是 POINT FAR *ptr; 的方便缩写,FAR 扩展为 far,后来扩展为 __far。这一切都很不雅,但过去更糟。【参考方案13】:

typedef 的目的是隐藏实现细节,但是 typedef 指针属性隐藏了太多,使代码更难阅读/理解。 所以请不要那样做。


如果你想隐藏实现细节(这通常是一件好事),不要隐藏指针部分。以标准FILE 接口的原型为例:

FILE *fopen(const char *filename, const char *mode);
char *fgets(char *s, int size, FILE *stream);

这里 fopen 返回一个 指针 指向某个结构 FILE(您不知道其实现细节)。也许FILE 不是一个很好的例子,因为在这种情况下,它可以与一些隐藏它是指针的事实的 pFILE 类型一起使用。

pFILE fopen(const char *filename, const char *mode);
char *fgets(char *s, int size, pFILE stream);

但是,这只会起作用,因为您永远不会弄乱直接指向的内容。根据我的经验,当您键入一些指针,您在某些地方修改代码时变得非常难以阅读。

【讨论】:

【参考方案14】:

前段时间,我会对这个问题回答“不”。现在,随着智能指针的兴起,指针不再总是用星号'*'定义。所以类型是否是指针没有什么明显的。

所以现在我想说:typedef 指针很好,只要明确它是“指针类型”。这意味着您必须专门为它使用前缀/后缀。不,例如,“p”不是一个足够的前缀。我可能会选择“ptr”。

【讨论】:

除非您不打算将其用作指针,在这种情况下您可以随意命名。如果它只是用来传递东西(比如 C 文件 *),它到底是什么重要吗? 好点。我试图在反对 typedef'ing 指针的缺点列表中添加一些优点......【参考方案15】:

一般来说,不会。在特定情况下,是的。

其他一些答案提到了几个结构,那就是指针-only 类型。我想到了几个仅指针类型的构造。如果有人想到更多,我会将它们添加到列表中。

不透明类型

这些类型的实现对用户完全隐藏。您通常会在标头中看到一个结构 typedef,但没有该结构的实现。因此,您不能取消引用这些类型的值。对这种类型进行操作的所有函数都采用指向这些类型的指针。在这些情况下添加指向 typedef 的指针是合适的。您经常会看到这些称为“句柄”的类型。

typedef struct handle_ * handle;

handle get_handle(string s);
void mutate_handle(handle h, value v);
void release_handle(handle h);

灵活的数组成员类型

另一种仅指针类型是使用灵活数组成员 (FAM) 定义的类型。 FAM 类型中的最后一个成员是不受约束的数组。您打算为这些类型动态分配存储空间,并且灵活数组被视为与结构内联。您可以访问 FAM 类型中的字段,但不能取消引用整个对象。在这里添加指向typedef的指针也是合适的。

typedef struct string 
    size_t capacity;
    size_t length;
    char buffer[];
 * string;

string string_new(void);
size_t string_length(string s);
void string_append(string * s, char c);
void string_delete(string * s);

【讨论】:

以上是关于typedef 指针是个好主意吗?的主要内容,如果未能解决你的问题,请参考以下文章

软删除是个好主意吗? [复制]

我可以将 typedef 结构与指针一起使用吗?

使用私有继承隐藏实现是个好主意吗?

同时读取多个文件是个好主意吗?

使用 Spring AOP 记录是个好主意吗?

为程序使用不同的 Python 脚本是个好主意吗? [关闭]