【问题标题】:ANSI C - Benefit of assigning alternative name to pointer type using typedef [duplicate]ANSI C - 使用 typedef 为指针类型分配替代名称的好处 [重复]
【发布时间】:2014-04-15 06:00:37
【问题描述】:

可能重复:
What is the right way to typedef a type and the same type's pointer?

我最近在我的项目中使用了 Libxml2,并注意到它使用如下类型定义:

typedef struct _xmlNode xmlNode
typedef xmlNode * xmlNodePtr

第一个 typedef 的好处是显而易见的。但是我不确定为什么要为 xmlNode * 分配一个替代名称。对我来说,使用 xmlNode * 比使用 xmlNodePtr 更明确和可读,但我可能会遗漏一些东西。

这个 typedef 会解决什么问题,又会带来什么好处?

【问题讨论】:

  • 这是一个品味问题。我同意你的立场(我讨厌xmlNodePtr-like typedef-s 的指针)。并且 GTk 不使用它(即使 GCC 内部有一个 typedef tree 和另一个 typedef gimple 用于指向复杂数据类型的指针)。
  • 这些类型的类型定义并不是特别有用。但是指针 typedef 真正有用的一种情况是函数指针,因为原始函数指针语法很难看。
  • FWIW,这是 Linux 内核开发人员的意见以及一些基本原理:kernel.org/doc/Documentation/CodingStyle

标签: c


【解决方案1】:

C API 通常提供不透明的句柄,这会阻止消费者询问它们是什么并试图戳进去。这些句柄是指针这一事实无关紧要,对消费者来说应该无关紧要,她也不应该为额外的星号带来的词汇混乱负担。

例如,完全可以通过定义句柄来编写 C++ 绑定:

typedef void * MyHandle;

现在我们给幸福的 C 消费者一些功能:

MyHandle create_gizmo();
void destroy_gizmo(MyHandle);
int do_magic(MyHandle, int, int);

就这么简单。用户立即看到如何使用它:

#include "MagicAPI.h"

MyHandle h = create_gizmo();

submit_result(do_magic(h, 12, 91));

destroy_gizmo(h);

C++ 库开发人员只需解开句柄并填充 API 函数(当然声明为 extern "C"):

#include "MagicAPI.h"
#include "SuperGizmo.hpp"

MyHandle create_gizmo() { return static_cast<MyHandle>(new SuperGizmo); }

void destroy_gizmo(MyHandle h) { delete static_cast<SuperGizmo *>(h); }

int do_magic(MyHandle h, int a, int b)
{
    return static_cast<SuperGizmo *>(h)->foo(a, b);
}

【讨论】:

  • 使用typedef void *MyHandle 可能会导致滥用函数,而typedef struct MyStruct *MyHandle 不会。
  • @JonathanLeffler:确实如此,但它使 C 和 C++ 互操作变得更加可疑,不是吗?
  • 我不确定它为什么会影响 C 和 C++ 互操作性。 AFAIK,您可以像在 C、AFAIK 中一样在 C++ 中转发声明(模命名空间,但这与手头的问题无关)。我确实为 struct MyStructMyHandle 使用了单独的名称...因为 typedef struct MyHandle *MyHandle; 对我来说是可恶的,并且可能会导致 C++ 编译器中的精神分裂症。
  • typedef void *MyHandle; 的更大问题是typedef void *YourHandle; 类型的变量可以传递给需要MyHandle 的函数,编译器无法警告您该问题。你不妨扔掉原型提供的类型安全。
  • @JonathanLeffler:那么您将如何调用 C++ 代码中使用的类型?我想不出一种可移植的、标准的命名方式。
【解决方案2】:

这个 typedef 会解决什么问题,又会带来什么好处?

我认为类型定义对象指针是不好的,不应该这样做。

首先它通过隐藏声明的对象是指针类型来改变 C 语言的语法。

第二个类型限定符(constvolatile)不能穿透 typedef。

如果我们以你为例:

typedef struct _xmlNode xmlNode;
typedef xmlNode * xmlNodePtr;

现在无法声明对象,因此指针是 const 使用 xmlNodePtr 别名。

const xmlNodePtr xp;

表示xpconst 而不是*xp

const xmlNode x = /* ... */;
const xmlNodePtr xp = &x;  // Error!

顺便说一句,在 Linux 内核编码风格中,他们还建议不要将 typedef 用于指针:

“将 typedef 用于结构和指针是一个错误。”

【讨论】:

  • 如果类型不透明,则无法通过值修改数据,因此const无法穿透typedef在客户端代码中并不重要。在类本身的实现中可能是一个更严重的问题。 (我通常还是不喜欢使用非函数指针类型的 typedef。)
  • 请注意,如果您采用内核编码风格,那么typedef struct _xmlNode xmlNode;typedef xmlNode * xmlNodePtr; 一样糟糕。好吧,也许不是同样糟糕,但同样被禁止。这可能会让那些想对指针进行类型定义的 C 程序员感到惊讶和不安,并导致他们完全抛弃内核编码风格的建议;-)
【解决方案3】:

对于某些人来说,名称中带有“ptr”的 typedef 比使用普通指针语法的声明更具可读性和“自然性”。如果你喜欢写作

foo* p;

而不是

foo *p;

那么指针 typedef 可能会吸引你,因为它专门避免了写入错误

foo* p, q;

你的意思

foo *p, *q;

你可以写

fooptr p, q;

【讨论】:

    【解决方案4】:

    有一些区别。

    首先,如果你在一行中做多个声明,下面两个sn-ps是等价的:

    char *c1, *c2, *c3, *c4;
    
    typedef char * charPtr;
    charPtr c1, c2, c3, c4; // I don't need to repeat the unary * everywhere
    

    这在一定程度上很有用,因为您可能会在尝试执行以下操作时遇到语义问题:

    char * c1, c2, c3, c4; // declares one char pointer and three chars
    

    其次,它们可以大大简化函数指针类型的定义,这为几乎无限的丑陋和肮脏提供了机会。

    float (*someCrazyFunction)(float(*)(), float) = foo; // WTF is this
    
    typedef float (*crazyFunction)(float(*)(), float);
    crazyFunction myCrazyFunction = foo; // easier to deal with
    

    Here's an ideone demonstrating all of this behavior.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-08-15
      • 2021-04-03
      • 1970-01-01
      • 2020-03-21
      • 2016-01-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多