【问题标题】:include guards not working包括不工作的警卫
【发布时间】:2012-10-19 20:08:12
【问题描述】:

我已经定义了一个 util.h 文件,其中包含我想在其他几个不同文件中使用的函数。这个头文件有一个包含保护,但是当我在两个不同的文件中使用它时,我得到一个multiple definition of... 错误。我做错了什么?

我已阅读 this 但这与变量声明/定义有关。 This 答案似乎更相关,但我不清楚如何解决这个问题。

// util.h
// include lots of standard headers
#include ...

#ifndef UTIL_H
#define UTIL_H

using namespace std;
// multiple definition of `randarr(int, int, int)`
int* randarr(int size, int min, int max) {
    int *ret = new int[size];
    for (int i=0; i<size; i++)
            ret[i] = (int) (((double) rand() / RAND_MAX) * max) + min;
    return ret;
}
// no error
template<typename T> void printarr(T* v, int begin, int end) {
    for (int i=begin; i<end; i++)
    cout << v[i] << " ";
    cout << endl;
}
// multiple definition of `is_prime(int)`
bool is_prime(int n) {
    if (n == 2 || n == 3 || n == 5) return true;
    if (n <= 1 || (n&1) == 0) return false;

    for (int i = 3; i*i <= n; i += 2)
            if (n % i == 0) return false;

    return true;
}
#endif

// example.cpp
#include ...// lots of standard includes
#include "util.h"
void f() {
    randarr(...);
    printarr(...);
    is_prime(...);
    ...
}

// Main.cpp
#include "util.h"
int main() {

}

【问题讨论】:

  • 你从 util.h 中包含的所有文件是否都包含保护。 2:也许有人在某个地方取消了 UTIL_H 宏的定义。
  • 你应该让randarr()成为一个内联函数!
  • 不值得回答,但没有人评论过头文件中的using namespace std;。正如我们在德克萨斯州所说,“拿一根绳子”。
  • 我将原型和实现分开,但在这种情况下内联所有函数效果最好,因为函数都很短

标签: c++ include compiler-errors


【解决方案1】:

包含守卫不是错误的原因;你违反了One Definition Rule。由于util.h被包含在2个源文件中,预处理每个源文件后创建的翻译单元将包含每个函数的定义,从而导致多定义错误。

要消除错误,请标记函数inline

inline int* randarr(int size, int min, int max) {
  // ...
}

template<typename T> 
inline void printarr(T* v, int begin, int end) {
  // ...
}

inline bool is_prime(int n) {
  // ...
}

【讨论】:

  • 当然,守卫是为了防止第二个定义,他们不是允许多个包含吗? inline 甚至在这里做了什么来解决这个问题?
  • @Troyseph 是的,包含防护可防止多重包含...在单个翻译单元中(大致为 .cpp 文件)。如果您在多个 cpp 文件中包含标头,它们将无法防止任何事情,在这种情况下,您将遇到多个定义的链接器错误,因为您违反了 ODR(ODR 违规不需要诊断,因此甚至不需要实现来检测和出错)。 inline 告诉链接器将多个定义视为单个定义并选择一个定义,从而避免错误。
  • 为简化起见,如果您在标头中放置前向声明并将实现放入 cpp 文件(通常与标头同名),则包含保护起作用。但是如果你把实现放在header中,你需要使用inline
【解决方案2】:

你得到一个 linker 错误,而不是编译器错误。您已经在util.h 文件中实现了函数randarr(),这意味着编译器在example.cppMain.cpp 中都看到了randarr() 的副本。当链接器将这些链接在一起时,它会抱怨,因为您不允许对同一函数有多个定义。

你有两个选择:

  • 在头文件中将randarr()声明为inline
  • randarr() 的定义移动到util.cpp 文件中

is_prime()应用相同的修复。

【讨论】:

    【解决方案3】:

    defined 在头文件中运行。这意味着,这些函数的代码包含在example.cppMain.cpp 中。这也意味着代码将被生成两次。这就是“多重定义”错误的原因。

    当您在单独的util.cpp 中仅定义函数randarr()is_prime() 一次时,错误将消失。

    【讨论】:

      【解决方案4】:

      您的标题应该只包含您的函数的原型。原型向其他文件描述您的功能,但不要实现它。唯一的例外是模板,因为每个模板特化都是在编译时构建的。

      如果你在头文件中实现你的函数,在链接器时,你会发现多次函数内容,这就是你面临错误的原因。

      randarris_prime 的实现移动到另一个文件,并将你的 util.h 转换为:

      #ifndef UTIL_H
      #define UTIL_H
      
      using namespace std;
      int* randarr(int size, int min, int max);
      template<typename T> void printarr(T* v, int begin, int end) {
          for (int i=begin; i<end; i++)
          cout << v[i] << " ";
          cout << endl;
      }
      bool is_prime(int n);
      #endif
      

      【讨论】:

      • 在头文件中定义(而不是仅仅声明)一个函数并没有错——只要你把它限定为inline。顺便说一句,在标头中定义的函数模板也需要限定为inline。如果您不这样做,您仍然会遇到关于违反 One Definition Rule 的链接器错误。
      • 将函数声明为inline 会改变编译器解释它的方式。并且在头文件中定义函数无论如何都会在可执行文件上复制代码,这根本不是一个好习惯。
      • 但是你对模板的看法是对的,我是在考虑模板类的情况下写的。
      • 如果这不是一个好习惯,那为什么 C++ 标准库和 Boost 都塞满了内联定义呢?如果函数很短且扇出率较低,则在头文件中定义函数通常是首选实现。
      • 我解释得不够清楚,请原谅我的英语。我的意思是,你不能对不了解头文件是什么的人说简单地内联他用来使其工作的函数。在优化方面,内联函数很有用,但你不能对初学者说“将所有实用函数放在头文件中并内联”
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-19
      • 1970-01-01
      • 2016-04-13
      • 1970-01-01
      相关资源
      最近更新 更多