为什么C/C++中的函数体放在单独的源代码文件而不是头文件中?

9
例如,当我在C++中定义一个类文件时,我总是将函数体放在类头文件(.h)中,与类定义一起。源代码文件(.cpp)是带有main()函数的文件。现在,这种做法是C++专业程序员常见的做法还是他们遵循分离头文件/源代码文件的惯例?
至于Native C,我确实注意到它们在GCC中已经完成(当然,在Windows的Visual Studio中为头文件)。
那么这只是一种惯例吗?还是有原因呢?

这只是惯例。 - Jake Freeman
4
常规往往有其原因。在这种情况下,有“非常好的理由”。 - Mooing Duck
请参见:https://dev59.com/znNA5IYBdhLWcg3wpvtg - Imran NZ
想一想,如果你把所有东西都放在头文件中,那么这意味着你只有一个.cpp文件和你的main()函数,必须直接或间接地包含整个代码。这对于小程序可能有效,但要用这种方式编译Linux内核,好运。 - Sam Varshavchik
这不仅仅是“惯例”。两者有不同的功能,因此程序员需要做出适当的选择。请参阅AnT的答案。 - Galik
3个回答

21

将函数主体放入.cpp文件中可以实现以下目的:

  1. 让编译器只解析和编译它们一次,而不是强制在包含头文件的每个地方都重新编译它们。此外,在头文件实现的情况下,链接器稍后还必须检测并消除到达不同对象文件的相同外部链接函数。

    由许多现代编译器实现的头文件预编译工具可能会显着减少重复重新编译相同头文件所需的浪费努力,但它们并不能完全消除这个问题。

  2. 将这些函数的实现隐藏起来,不让模块或库的未来用户看到。实现隐藏技术有助于强制执行某些编程原则,从而减少模块之间的寄生性互相依赖,从而导致更清晰的代码和更快的编译时间。

    我甚至认为即使用户可以访问库的完整源代码(也就是说,他们对其没有真正的“隐藏”),通过头文件显示出来的内容和不应显示的内容之间的清晰分离有益于库的自我文档化属性(尽管这样的分离也可以在仅包含头文件的库中实现)。

  3. 使一些函数对外部世界“不可见”(即具有内部链接性质,在类方法的示例中不是立即相关的)。

  4. 驻留在特定翻译单元中的非内联函数可以受到某些上下文相关的优化。例如,两个具有相同尾部部分的不同函数最终可能会“共享”实现这些相同尾部的机器代码。

    在头文件中声明为内联的函数将在不同的翻译单元(即在不同的上下文中)中多次编译,并且稍后必须由链接器消除,这使得更难(如果可能的话)利用此类优化机会。

  5. 其他我可能错过的原因。


1
为了让库能够与其相应的头文件一起分发,以便其他人可以在他们的代码中使用它们。 - Andrei Tumbar
非常好的答案。感谢您解释了实际的原因,因为这不仅仅是惯例,而且还涉及计算效率。 - SavedbyZer0
总结很出色,用户名也很巧妙,避免了老式新斯科奇在内衣中的问题... :) - undefined

4

这是一种约定,但也取决于具体的需求。例如,如果您正在编写一个库,希望功能快速(内联),并且设计此库供他人使用为一个简单的“header only”库,则可以在头文件中编写所有代码。

另一方面,如果您正在编写将链接静态或动态库的库,并且您正在尝试从用户封装内部对象数据,则编写函数 - 类成员函数等方式为它们应该执行的操作,使得您的库代码的用户不必担心其实际实现细节,只需要知道函数和类的接口即可。这样,您将同时拥有头文件和实现文件。

如果您将函数定义与其声明放置在头文件中,则它们将被内联,并且应该运行更快,但您的可执行文件会更大,并且每次都必须重新编译。实现细节也对用户暴露出来。

如果您将函数定义放置在相关代码文件中,则它们将不会内联,您的代码将更小,可能运行略慢,但您只需要编译一次。实现细节被隐藏并抽象化了。


1
在'C'语言中,没有任何理由将函数体放在头文件中。如果头文件被包含在多个'C'文件中,这将迫使编译器定义该函数多次。如果该函数是“静态的”,则程序中将有多个副本;如果是全局的,链接器将会报错。
对于C++也是类似的情况。例外情况是类的“内联”成员和一些模板实现。
如果您在'cpp'文件中定义了一个临时类,那么在其中定义函数体就完全可以。

非常简洁的回答。谢谢。 - SavedbyZer0

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