在头文件中定义C++函数是一种好的实践吗?

56
我在想,将C++的普通函数(而不是类中的方法)存储在头文件中是否是一个好的做法。
例如:
#ifndef FUNCTIONS_H_INCLUDED
#define FUNCTIONS_H_INCLUDED

int add(int a, int b)
{
   return a + b;
}

#endif

然后像这样使用它:

#include <iostream>
#include "Functions.h"

int main(int argc, char* args[])
{
    std::cout << add(5, 8) << std::endl;
    return 1;
}

这是一个好的做法吗?

15
不,你不可避免地会违反一次定义规则。 - chris
1
想象一下,如果头文件被包含在两个或更多的源文件中会发生什么。 - Some programmer dude
3
为什么?难道不是为了防止这种情况而设置 include guards 嘛? - Daniel Daranas
14
@DanielDaranas 不,头文件保护宏的作用是防止在同一个源文件中多次包含相同的头文件。它不会防止在多个源文件中包含同一个头文件(如果将源文件单独编译成目标文件,那么这该怎么做呢?)还要了解翻译单元(还有提问者:您也应该阅读一下该链接)。 - Some programmer dude
12
为避免违反单一定义规则,您需要将函数标记为inline - juanchopanza
显示剩余6条评论
3个回答

85
如果你想在多个源文件(或者说翻译单元)中使用一个函数,那么你需要在头文件中放置一个函数声明(即函数原型),并将定义放在一个源文件中。
然后在构建时,你首先将源文件编译为目标文件,然后将目标文件链接到最终的可执行文件中。
示例代码:
  • Header file

      #ifndef FUNCTIONS_H_INCLUDED
      #define FUNCTIONS_H_INCLUDED
    
      int add(int a, int b);  // Function prototype, its declaration
    
      #endif
    
  • First source file

      #include "functions.h"
    
      // Function definition
      int add(int a, int b)
      {
          return a + b;
      }
    
  • Second source file

      #include <iostream>
      #include "functions.h"
    
      int main()
      {
          std::cout << "add(1, 2) = " << add(1, 2) << '\n';
      }
    
你如何构建它非常取决于你的环境。如果你使用IDE(如Visual Studio、Eclipse、Xcode等),那么你需要将所有文件放置在项目的正确位置。
如果你是在命令行中构建,例如在Linux或OSX中,则可以执行以下操作:
$ g++ -c file1.cpp
$ g++ -c file2.cpp
$ g++ file1.o file2.o -o my_program

-c标志告诉编译器生成一个目标文件,名称与源文件相同,但后缀为.o。最后一个命令将两个目标文件链接在一起形成最终的可执行文件,并将其命名为my_program(这就是-o选项所做的工作,告诉输出文件的名称)。


2
@RoyAssis 在这种简单的情况下可能不需要。但是想想当函数实现需要一些结构、宏或其他在头文件中定义或声明的东西时呢?你会选择包含已经编写好了所有内容的头文件,还是将其自己复制粘贴到源文件中?如果你选择复制粘贴,请考虑一下如果你需要修改头文件中的某些内容,但忘记在源文件中进行相同的编辑会发生什么? - Some programmer dude
函数add是一行代码。在什么情况下将函数定义放在头文件中是可以接受的?我听说过,短小的成员函数可以在头文件中定义,如果你不需要#include除了声明该函数所需的内容之外的任何其他内容。这样的规则有意义吗? - mercury0114
1
如果你在头文件中定义(实现)一个函数,它必须被标记为inlinestatic,否则如果你在多个翻译单元中包含该头文件,就会出现多重定义错误。还可能有其他考虑因素,这完全取决于使用情况。 - Some programmer dude
@Someprogrammerdude,是的,我的问题是关于“inline”函数的。 - mercury0114
1
如果函数是在类内定义(实现)inline,那么编译器会自动标记它们为“inline”,并且它们可以像任何其他函数一样使用。如果您将函数定义在类外部,则必须自己将其标记为“inline”。这可以防止多重定义错误。 - Some programmer dude
显示剩余3条评论

37

在预处理之后,每个源文件都将包含头文件。然后,在链接阶段,你会遇到多个同名函数的多次定义错误。

使用inlinestatic可以消除链接错误。如果不希望该函数是inline,最好在头文件中声明该函数,并在单个源文件中定义它并进行链接。

如果将函数声明为inline,则源文件中每个函数调用都将替换为inline函数中的代码。因此,不会定义额外的符号。

如果将函数声明为static,则函数符号将不会从翻译单元导出。因此,不会有重复的符号。

此外,完全在类/结构体/联合定义内部定义的函数,无论是成员函数还是非成员friend函数,都隐式地是一个inline函数。因此,在这些情况下,不需要显式写inline


5
这是一个相当不错的解释,说明了正在发生的事情。 - C--
inline 可以消除链接错误,但它不会用 inline 函数内部的代码替换每个函数调用 - 它不会将其内联。 - Tachi

27

不行。如果从两个文件导入相同的头文件,则会导致函数重定义。

但是,如果该函数是内联函数,则这种情况很常见。每个文件都需要其定义才能生成代码,因此人们通常将定义放在头文件中。

使用 static 也可以工作,因为静态函数不会从对象文件导出,并且以这种方式不能干扰链接期间具有相同名称的其他函数。

在头文件中定义成员函数也可以,因为C++标准将其视为inline


使用 static 替代 inline 也可以,但原因不同。 - nwp
感谢您。扩展答案以解释这个。 - Alexey Shmalko
2
类成员函数的细节非常重要。+1 - DeepBlue

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