多重定义错误c ++

时间:2016-01-05 14:50:55

标签: c++ header-files multiple-definition-error

我的'Headers.h'文件包含基本的c ++标题

#include <iostream>
#include <cstring>
// and many header files.

为文件存在检查写了一个函数定义并将其保存在'common_utility.h'中 - ifFileExist()

common_utility.h

bool ifFileExist()
{
   // ... My code
}

为A类编写代码 classA.h

class A
{
// Contains class A Declarations.

};

classA.cpp

// Contains
#include "Headers.h"
#include "common_utility.h"
#include "classA.h"

// class A Method definition

为B类写了代码 我在B级使用A级。

classB.h

 class B
    {
// Contains class A Declarations.
}

classB.cpp

// Contains
#include "Headers.h"
#include "common_utility.h"
#include "classA.h"
#include "classB.h"

// class B Method definition
// calling the function ifFileExist() in class B also.

为主程序编写代码 main.cpp中

// Contains
#include "Headers.h"
#include "common_utility.h"
#include "classA.h"
#include "classB.h"

// I am using class A and Class B in main program
// calling the function ifFileExist() in Main program also.

当我将整个程序编译为

  

g ++ -std = c ++ 0x classA.cpp classB.cpp main.cpp -o main

我收到以下错误。

  

在函数ifFileExist()': classB.cpp:(.text+0x0): multiple definition of ifFileExist()'   /tmp/ccHkDT11.o:classA.cpp:(.text+0x2b6e):首先在这里定义

所以我在Headers.h中将ifFileExist()函数称为extern。

extern bool ifFileExist();

但我仍然得到同样的错误。

我在每个.cpp文件中包含'Headers.h'。该文件包含基本的c ++库。但我没有得到任何多个定义错误的头文件。 但只有在我自己的函数中,我才会收到错误'多重定义'。

我想使用'common_utility.h'文件,我需要使用它。如果我不需要在主程序中使用common_utility函数,那么我就不应该包含它。

我希望我的程序能够在以下每种情况下运行。

  

g ++ -std = c ++ 0x classA.cpp main.cpp -o main
  g ++ -std = c ++ 0x classB.cpp&gt; main.cpp -o main
  g ++ -std = c ++ 0x classA.cpp classB.cpp main.cpp -o main

在任何情况下我都不应该得到多重定义错误。我现在应该怎么做?

3 个答案:

答案 0 :(得分:13)

由于我找不到任何完整的(在我看来)这个问题的副本,我将写一个(希望)权威和完整的答案。

什么是一个定义规则以及我为什么要关心

一个定义规则,通常被称为ODR,是一个规则,它规定(简化)程序中使用的任何实体(非正式术语)应该只定义一次,并且只定义一次。不止一次定义的实体通常会导致编译或链接器错误,但有时可能会被编译器检测不到并导致非常难以跟踪的错误。

我不会在这里正式定义实体,但可以将其视为函数,变量或类。在进一步研究之前,我应该非常清楚地理解C ++中定义声明之间的区别,因为虽然禁止双重定义,但双重声明通常是不可避免的。

定义与声明

代码中使用的每个实体都应该在给定的翻译单元中声明(翻译单元通常是cpp源文件以及其中包含的所有头文件,直接或间接通过其他头文件) 。根据实体本身声明权利的方式不同。请参阅下文,了解如何声明不同类型的实体。实体通常在头文件中声明。由于大多数复杂的应用程序中都有多个转换单元(多个cpp文件),并且不同的cpp文件通常包含相同的标头,因此应用程序可能会对所使用的许多实体进行多次声明。就像我上面说的那样,这不是问题。

应用程序中使用的每个实体必须一次定义一次。术语“应用程序”&#39;在这里使用有点松散 - 例如,库(静态和动态)可以在其中保留未定义的实体(此时通常称为符号),并且链接到使用动态库的可执行文件可以具有未定义的符号好。相反,我指的是应用程序是一个终极运行的某种东西,所有的库已经静态或动态链接到它,并且符号已经解析。

值得注意的是,每个定义都可以作为一个声明,这意味着,无论何时定义某些内容,您也会声明同样的事情。

与声明一样,定义实体的方式因实体类型而异。以下是如何根据它的类型声明/定义3种基本类型的实体 - 变量,类和函数。

变量

使用以下构造声明变量:

extern int x;

这声明了一个变量x。它没有定义它!下面的一段代码将被编译好,但是尝试链接它而没有任何其他输入文件(例如,使用g++ main.cpp)将由于未定义的符号而产生链接时错误:

extern int x;
int main() {
    return x;
}

以下代码定义变量x:

int x;

如果将这一行放入文件x.cpp中,并且这个文件与main.cpp一起使用g++ x.cpp main.cpp -o test编译/链接在一起,它将编译和链接没有问题。您甚至可以运行生成的可执行文件,如果要在运行可执行文件后检查退出代码,您会注意到它是0.(因为全局变量x将默认初始化为0)。

功能

通过提供原型来声明函数。典型的函数声明如下所示:

double foo(int x, double y);

此构造声明一个函数foo,返回double并接受两个参数 - 一个类型为int,另一个类型为double。该声明可以多次出现。

以下代码定义上面提到的foo

void foo(int x, double y) {
    return x * y;
}

定义只能在整个应用程序中出现一次。

函数定义对变量定义有一个额外的怪癖。如果将foo的上述定义放入头文件foo.h中,而后者将由两个cpp文件1.cpp2.cpp包含在一起,这两个文件与一起编译/链接在一起g++ 1.cpp 2.cpp -o test你会有一个链接器错误,说foo()定义了两次。可以使用以下形式的foo声明来阻止这种情况:

inline void foo(int x, double y) {
    return x * y;
}

请注意inline。它告诉编译器的是foo可以包含多个.cpp文件,并且此包含不应该产生链接器错误。编译器有多种选择可以实现这一点,但可以依靠它来完成它的工作。注意,在同一翻译单元中将此定义两次仍然是错误的!例如,以下代码将产生编译器错误

inline void foo() { }
inline void foo() { }

值得注意的是,类中定义的任何类方法都是内联的,例如:

class A {
public:
    int foo() { return 42; }
};

这里定义了A :: foo()inline

类新

Classess通过以下构造声明:

class X;

以上声明声明类X(此时X正式称为不完整类型),因此可以在有关其内容的信息时使用,例如它的大小或它的成员是不需要的。例如:

X* p; // OK - no information about class X is actually required to define a pointer to it
p->y = 42; // Error - compiler has no idea if X has any member named `y`

void foo(X x); // OK - compiler does not need to generated any code for this

void foo(X x) { } // Error - compiler needs to know the size of X to generate code for foo to properly read it's argument
void bar(X* x) { } // OK - compiler needs not to know specifics of X for this

每个人都熟知类的定义,并遵循以下结构:

class X {
   public:
   int y;
};

这使得定义了类X,现在它可以在任何上下文中使用。一个重要的注意事项 - 类定义必须是每个tralnlation单元唯一的,但不必每个应用程序是唯一的。也就是说,每个翻译单元只能定义一次X,但它可以在链接在一起的多个文件中使用。

如何正确遵循ODR规则

每当在生成的应用程序中多次定义同一实体时,就会发生所谓的 ODR违规。大多数情况下,链接器会看到违规并会抱怨。但是,有些情况下,ODR违规不会破坏链接,而是会导致错误。例如,当将定义全局变量X的相同.cpp文件放入应用程序和动态库(可以按需加载dlopen)时,可能会发生这种情况。 (你真的花了几天时间试图追踪因此发生的错误。)

更常见的ODR违规原因是:

在同一范围内的同一文件中定义两次相同的实体

int x;
int x; // ODR violation

void foo() {
   int x;
} // No ODR violation, foo::x is different from x in the global scope

预防:不要这样做。

同一个实体定义了两次,当它应该被声明为

(in x.h)
int x;

(in 1.cpp)
#include <x.h>
void set_x(int y) {
   x = y;
}

(in 2.cpp)
#include <x.h>
int get_x() {
    return x;
}

虽然上述代码的智慧充其量是值得怀疑的,但是它可以说明ODR规则。在上面的代码中,变量x应该在两个文件1.cpp和2.cpp之间共享,但编码不正确。相反,代码应该遵循:

(in x.h)
extern int x; //declare x

(in x.xpp)
int x; // define x

// 1.cpp and 2.cpp remain the same

预防 知道你在做什么。声明要声明的实体,不要定义它们。 如果在上面的示例中我们使用函数而不是变量,如下所示:

(in x.h)
int x_func() { return 42; }

我们会遇到一个问题,可以通过两种方式解决(如上所述)。我们可以使用inline函数,或者我们可以将定义移动到cpp文件:

(in x.h)
int x_func();

(in x.cpp)
int x_func() { return 42; } 

相同的头文件包含两次,导致同一个类定义两次 这很有趣。想象一下,您有以下代码:

(in a.h)
class A { };

(in main.cpp)
#include <a.h>
#include <a.h> // compilation error!

上面的代码很少以书面形式出现,但通过中间文件包含两次相同的文件非常容易:

(in foo.h)
#include <a.h>

(in main.cpp)
#include <a.h>
#include <foo.h>

预防传统的解决方案是使用所谓的 include guards ,即一种特殊的预处理器定义,它可以防止双重包含。在这方面,a.h应该重做如下:

(in a.h)
#ifndef INCLUDED_A_H
#define INCLUDED_A_H

class A { };

#endif

上面的代码会阻止a.h多次包含在同一个翻译单元中,因为INCLUDED_A_H将在首次包含后定义,并且在所有后续的#ifndef上都会失败。

有些编译器公开了控制包含的其他方法,但到目前为止,保护程序仍然是在不同编译器中统一执行的方法。

答案 1 :(得分:0)

在实际编译源代码之前,编译单元是从.cpp文件生成的。这基本上意味着计算所有预处理器指令:所有#include将替换为包含文件的内容,所有#define'd值将替换为相应的表达式,所有#if 0 ... #endif将是因此,在你的情况下执行这一步之后,你将得到两段没有任何预处理程序指令的C ++代码,它们都有相同函数bool ifFileExist()的定义,这就是你得到这个多重定义错误的原因。

快速解决方案是将其标记为inline bool ifFileExist()。基本上你要求编译器用函数本身的内容替换所有相应的函数调用。

另一种方法是在common_utility.h中实现您的功能声明,并将定义移至common_utility.cpp

答案 2 :(得分:-1)

最简单的解决方法是:插入关键字inlineinline bool ifFileExist()