我的'Headers.h'文件包含基本的c ++标题
#include#include // 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> main.cpp -o main
g ++ -std = c ++ 0x classA.cpp classB.cpp main .cpp -o main
在任何情况下我都不应该得到多个定义错误.我现在应该怎么做?
由于我找不到任何完整的(在我看来)这个问题的副本,我将写一个(希望)权威和完整的答案.
一个定义规则,通常被称为ODR,是一个规则,它规定(简化)程序中使用的任何实体(非正式术语)应该只定义一次,并且只定义一次.多次定义的实体通常会导致编译或链接器错误,但有时可能会被编译器检测不到并导致非常难以跟踪的错误.
我不会在这里正式定义实体,但可以将其视为函数,变量或类.在进一步研究之前,应该非常清楚地理解C++中定义和声明之间的区别,因为虽然禁止双重定义,但双重声明通常是不可避免的.
代码中使用的每个实体都应在给定的转换单元中声明(转换单元通常是cpp源文件以及其中包含的所有头文件,直接或间接通过其他头文件).根据实体本身声明权利的方式不同.请参阅下文,了解如何声明不同类型的实体.实体通常在头文件中声明.由于大多数复杂的应用程序中都有多个转换单元(多个cpp文件),并且不同的cpp文件通常包含相同的标头,因此应用程序可能会对所使用的许多实体进行多次声明.就像我上面说的那样,这不是问题.
应用程序中使用的每个实体必须只定义一次.这里使用的术语"应用程序"有点松散 - 例如,库(静态和动态)可以在其中保留未定义的实体(此时通常称为符号),并且链接到使用动态库的可执行文件可以也有一个未定义的符号.相反,我指的是应用程序是一个终极运行的东西,所有的库已经静态或动态链接到它,并解决了符号.
值得注意的是,每个定义都可以作为声明,这意味着,无论何时定义某些内容,您都会声明同样的事情.
与声明一样,定义实体的方式因实体类型而异.以下是如何根据类型声明/定义3种基本类型的实体 - 变量,类和函数.
使用以下构造声明变量:
extern int x;
这声明了一个变量x.它没有定义它!下面的一段代码将被编译好,但是尝试链接它而没有任何其他输入文件(例如,with 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.cpp
并且2.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违规不会破坏链接,而是导致错误.例如,当将定义全局变量X的相同.cpp文件放入应用程序和动态库(可根据需要加载dlopen
)时,可能会发生这种情况.(你的trully花了几天时间试图追踪因此发生的错误.)
更常见的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) #includevoid set_x(int y) { x = y; } (in 2.cpp) #include 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#include // compilation error!
上面的代码很少以书面形式出现,但通过中间文件包含两次相同的文件非常容易:
(in foo.h) #include(in main.cpp) #include #include
预防传统的解决方案是使用所谓的包含保护,即一种特殊的预处理器定义,它可以防止双重包含.在这方面,啊应该重做如下:
(in a.h) #ifndef INCLUDED_A_H #define INCLUDED_A_H class A { }; #endif
上面的代码将阻止ah多次包含在同一个翻译单元中,因为INCLUDED_A_H
它将在首次包含后被定义,并且将#ifndef
在所有后续的包含中失败.
一些编译器揭示了控制包含的其他方法,但到目前为止,保护程序仍然是在不同编译器中统一执行的方法.