一个什么都不做的函数有什么实际用途吗?

Epi*_*m7c 79 c function

运行时不执行任何操作的函数有什么用处,即:

void Nothing() {}
Run Code Online (Sandbox Code Playgroud)

请注意,我不是在谈论等待一定时间的函数,例如sleep(),只是需要编译器/解释器给出的时间。

dbu*_*ush 129

这样的函数作为回调函数可能是必要的。

假设您有一个如下所示的函数:

void do_something(int param1, char *param2, void (*callback)(void))
{
    // do something with param1 and param2
    callback();
}
Run Code Online (Sandbox Code Playgroud)

该函数接收一个指向随后调用的函数的指针。如果您不是特别需要使用此回调执行任何操作,则可以传递一个不执行任何操作的函数:

do_something(3, "test", Nothing);
Run Code Online (Sandbox Code Playgroud)

  • 如果您传入某种不使用回调的指示器,则任何选项都要求编写接受回调的函数的人考虑到有时用户可能不想执行任何操作的可能性,然后专门编写一些代码来支持这种可能性。即使 API 实现者没有预见到这种用法(或者您只是不确定他们是否预见到了这种用法,这是空指针思想的一大弱点,传递一个无所事事的函数仍然有效;API 签名不会进行通信)是否正确处理 null)。所以它的耦合度要低一些。 (36认同)
  • @Elliott 或者更好的是,该函数可以接受回调的“NULL”指针,并在调用之前检查该值。 (26认同)
  • 这让我想起了你有时在试卷上看到的“此页故意留白”文字,以明确学生并没有意外地得到一张空白纸 (12认同)
  • 只是为了扩展这个非常简洁的答案:我们*可以*通过添加布尔参数来提供相同的功能;像 `requires_callback` 这样的东西,并在函数中使用 if 语句......但它会更慢,而且使用起来更痛苦! (7认同)
  • 我可以添加另一个有效的用例吗?我公司的软件大量使用插件。每个插件都有一个 API。假设您可以在插件 foo/bar 和 foo/baz 之间进行选择。插件 bar 可能会对某个特定功能执行某些操作,而插件 baz 可能对该功能不执行任何操作,但这两个插件都需要将其实现为插件 API 的一部分。它在内部使用回调函数。 (5认同)
  • @Rowan,我参与了一个项目,技术负责人认为我们需要_正是如此_,并将所有可选回调更改为强制(这在 Typescript 中,签名很清楚,空值已被处理),使用“doNothing”(或“bounce”)它只是显式地返回了它的参数),在他审查和批准了太多次代码之后,结果发现忘记了必要的回调,而不是故意选择不使用回调。(我们正在与一个国际团队合作,因此就愚蠢的错误进行此类反复讨论是很痛苦的。) (3认同)
  • @user11153这个问题是关于C的,所以其他语言有什么并不重要。在 C 语言中,“NULL”非常适合作为指针的哨兵值。 (2认同)

abe*_*nky 47

当我创建包含函数指针的表时,我确实使用空函数。

例如:

typedef int(*EventHandler_Proc_t)(int a, int b); // A function-pointer to be called to handle an event
struct 
{
   Event_t             event_id;
   EventHandler_Proc_t proc;
}  EventTable[] = {    // An array of Events, and Functions to be called when the event occurs
    {  EventInitialize, InitializeFunction },
    {  EventIncrement,  IncrementFunction  },
    {  EventNOP,        NothingFunction    },  // Empty function is used here.
};
Run Code Online (Sandbox Code Playgroud)

在此示例表中,我可以放置NULL,NothingFunction并在调用之前检查是否.proc为。NULL但我认为在表中放置一个不执行任何操作的函数可以使代码更简单。

  • 当没有 NULL 检查时,不仅代码更干净,而且还有一点性能优势。对于绝大多数情况,这并不重要,但如果您正在编写中断处理程序状态机,则 if 测试损失可能会很大。它提供了确定性和统一的执行行为,这通常是良好的特征,并且对于功能良好的中断处理程序来说可能至关重要。因此,当您可以选择 A 或 B,并且在一般情况下差异不大,但在某些(罕见)情况下 B 比 A 好得多时,那么 B 应该是您的首选方法。 (5认同)

Jos*_*hua 37

是的。很多事情都希望有一个函数来通知发生的某些事情(回调)。不执行任何操作的函数是表达“我不关心这个”的好方法。

我不知道标准库中有任何示例,但许多构建在其之上的库都有事件的函数指针。

例如,glib 定义了一个回调“GLib.LogFunc(log_domain, log_level, message, *user_data)”来提供记录器。空函数将是您在禁用日志记录时提供的回调。

  • @Joshua:标准库确实有[`signal()`](https://en.cppreference.com/w/c/program)。另外,如果您使用的是附件 K,则 [constraint handlers](https://en.cppreference.com/w/c/error/set_constraint_handler_s)。 (7认同)
  • 标准库中的一个示例是“信号”处理程序。 (4认同)
  • @YakovGalka:请输入 SIG_IGN,这是 POSIX 库而不是标准库。 (4认同)
  • @DevSolar:事实证明,这是它说它有一些东西但实际上没有的地方之一。尝试过在 DOS 上使用它吗?这不起作用。发生浮点异常不会引发 SIGFPE。我尝试过这个。那里有一个 SIGTERM。它在 DOS 和 Win32 上都毫无用处。 (2认同)

Dan*_*ins 31

一个用例可能是作为临时存根一种用例是在程序开发过程中

如果我正在进行一定量的自上而下的开发,那么我通常会设计一些函数原型,编写主函数,然后想要运行编译器以查看到目前为止是否有任何语法错误。为了使编译发生,我需要实现相关的函数,我将通过最初创建不执行任何操作的空“存根”来实现。一旦我通过了编译测试,我就可以继续一次充实一个功能。

我所教的Gaddis 教科书《Starting with C++: From Control Structures Through Objects》是这样描述它们的(第 6.16 节):

存根是一个虚拟函数,它被调用而不是它所代表的实际函数。它通常会显示一条测试消息,确认它已被调用,仅此而已。

  • ..事实上,各种框架都有脚手架、骨架、模板,其功能标记为“将代码放在这里”,这些功能不一定会被使用,但由于发生了预定的整体处理序列而存在。如果从不需要该代码,它们会在生产中保留存根,特别是对于回调之前和之后的预处理和后处理,特别是如果有效负载块的编码是自动化的或在封闭的库中。 (7认同)

Sha*_*ger 13

接受参数但不对其执行任何操作的函数可以与执行有用操作的函数配对使用,这样即使使用无操作函数,参数仍会被计算。这在日志记录场景中非常有用,在这种情况下,仍然必须对参数进行求值以验证表达式是否合法并确保发生任何重要的副作用,但日志记录本身并不是必需的。当编译时日志记录级别设置为不需要该特定日志语句的输出的级别时,预处理器可能会选择无操作函数。


Dav*_*lor 13

我记得,Lions' Commentary on UNIX 6th Edition 中有两个空函数,附有 Source Code,以及本世纪初重新发行的介绍,名为 Ritchie、Kernighan 和 Thompson,就在上面。

吞噬其参数并且不返回任何内容的函数实际上在 C 中无处不在,但没有显式写出,因为它几乎在每一行都隐式调用。在传统 C 语言中,此空函数最常见的用途是不可见地丢弃任何语句的值。但是,从 C89 开始,可以将其明确拼写为(void)lint每当函数返回值被忽略而没有显式地将其传递给这个不返回任何内容的内置函数时,该工具就会发出抱怨。其背后的动机是试图阻止程序员默默地忽略错误条件,并且您仍然会遇到一些使用编码风格的旧程序,(void)printf("hello, world!\n");.

这样的函数可用于:

  • 回调(其他答案已经提到)
  • 高阶函数的论证
  • 对框架进行基准测试,无需执行空操作的开销
  • 具有正确类型的唯一值来与其他函数指针进行比较。(特别是在像 C 这样的语言中,所有函数指针都是可转换的并且可以相互比较,但是函数指针和其他类型的指针之间的转换是不可移植的。)
  • 函数式语言中单例值类型的唯一元素
  • 如果传递一个严格评估的参数,这可能是一种丢弃返回值但执行副作用并测试异常的方法
  • 虚拟占位符
  • 证明类型化 Lambda 演算中的某些定理


Tre*_*tni 12

不执行任何操作的函数的另一个临时用途可能是存在一行来放置断点,例如,当您需要检查传递到新创建的函数的运行时值时,以便您可以更好地决定执行哪些操作您要放入其中的代码将需要访问。就我个人而言,我喜欢使用自分配,即i = i当我需要这种断点时,但无操作函数可能也可以工作。

void MyBrandNewSpiffyFunction(TypeImNotFamiliarWith whoKnowsWhatThisVariableHas)
{
  DoNothing(); // Yay! Now I can put in a breakpoint so I can see what data I'm receiving!
  int i = 0;
  i = i;    // Another way to do nothing so I can set a breakpoint
}
Run Code Online (Sandbox Code Playgroud)

  • 我同意调试器应该允许您在任意位置放置断点。但我们现在就在这里。无论如何,谁说过一流的语言特性?OP询问无操作函数可能有用的原因,这当然适用。 (7认同)
  • 您的调试器应该允许您在任意位置放置断点。在底层,它会执行类似发出“int 3”指令(在 x86 世界中)之类的操作。这是工具不足的解决方法,而不是一流语言功能的原因。即使您的工具不足,您也可以插入几乎无限的无操作,这些操作将是有效的 C,即使 C 不允许空函数。 (3认同)

Sim*_*ter 6

从语言律师的角度来看,不透明的函数调用插入了优化障碍。

例如:

int a = 0;

extern void e(void);

int b(void)
{
    ++a;
    ++a;
    return a;
}

int c(void)
{
    ++a;
    e();
    ++a;
    return a;
}

int d(void)
{
    ++a;
    asm(" ");
    ++a;
    return a;
}
Run Code Online (Sandbox Code Playgroud)

++a函数中的表达式可以b合并到a += 2,而在c函数中,a需要在函数调用之前更新并在之后从内存中重新加载,因为编译器无法证明e没有访问,类似于函数中的a(非标准)。asm(" ")d

  • @Peter-ReinstateMonica:几十年前,人们观察到“每个程序都至少有一个错误,并且可以通过至少一条指令来缩短——通过归纳,我们可以推断出每个程序都可以简化为一条指令,而这并不需要一个指令。”行不通。” 现代编译器的目标是找到该程序。 (10认同)
  • 我认为这可能因语言而异。对于外部链接语言来说确实如此,但并非对于所有编译语言都如此。 (3认同)

kac*_*123 5

在嵌入式固件世界中,它可用于添加由于某些硬件原因所需的微小延迟。当然,这也可以连续调用多次,从而使程序员可以扩展此延迟。


bta*_*bta 5

空函数在特定于平台的抽象层中并不罕见。通常有些功能只在某些平台上需要。例如,函数void native_to_big_endian(struct data* d)在小端 CPU 上可能包含字节交换代码,但在大端 CPU 上可能完全为空。这有助于保持业务逻辑与平台无关且可读。我还看到过这样的任务,例如将本机文件路径转换为 ​​Unix/Windows 风格、硬件初始化函数(当某些平台可以使用默认值运行而其他平台必须主动重新配置时)等。