多重定义错误,包括带有来自多个源的内联代码的c ++头文件

时间:2008-10-17 12:57:20

标签: c++

我有一个包含类的c ++头文件。 我想在几个项目中使用这个类,我不想为它创建一个单独的库,所以我将两个方法声明和定义放在头文件中:

// example.h
#ifndef EXAMPLE_H_
#define EXAMPLE_H_
namespace test_ns{

class TestClass{
public:
    void testMethod();
};

void TestClass::testMethod(){
    // some code here...
}

} // end namespace test_ns
#endif

如果在同一项目中我从多个cpp文件中包含此标头,则会收到错误“multiple definition of test_ns::TestClass::testMethod()”,而如果我将方法定义放在类主体中,则不会发生这种情况:

// example.h
#ifndef EXAMPLE_H_
#define EXAMPLE_H_
namespace test_ns{

class TestClass{
public:
    void testMethod(){
        // some code here...
    }
};

} // end namespace test_ns
#endif

由于类是在命名空间内定义的,这两种形式不应该是等价的吗?为什么在第一种情况下认为方法定义了两次?

6 个答案:

答案 0 :(得分:22)

这些不等同。给出的第二个示例在方法上有一个隐含的“内联”修饰符,因此编译器将自己协调多个定义(如果不能内联,很可能与方法的内部链接)。

第一个示例不是内联的,因此如果此标头包含在多个翻译单元中,那么您将有多个定义和链接器错误。

此外,应始终保护标头以防止同一翻译单元中出现多个定义错误。这应该将您的标题转换为:

#ifndef EXAMPLE_H
#define EXAMPLE_H

//define your class here

#endif

答案 1 :(得分:21)

类体内部被编译器认为是内联的。 如果您在body之外实现,但仍然在header中,则必须将该方法明确标记为“inline”。

namespace test_ns{

class TestClass{
public:
    inline void testMethod();
};

void TestClass::testMethod(){
    // some code here...
}

} // end namespace test_ns

修改

对于我自己来说,通过认识到编译器看不到像头文件这样的东西,通常有助于解决这些类型的编译问题。头文件是预处理的,编译器只看到一个包含每个(递归)包含文件中每一行的大文件。通常,这些递归包含的起点是正在编译的cpp源文件。 在我们公司,即使是一个看起来很小的cpp文件也可以作为300000行怪物呈现给编译器。

因此,当一个未声明为内联的方法在头文件中实现时,编译器最终可能会在预处理文件中看到void TestClass :: testMethod(){...}几十次。现在您可以看到这没有意义,与在一个源文件中多次复制/粘贴它时获得的效果相同。 即使你成功只在每个编译单元中使用一次,通过某种形式的条件编译(例如使用包含括号),链接器仍然会发现此方法的符号位于多个编译单元(目标文件)中。

答案 2 :(得分:3)

不要将函数/方法定义放在头文件中,除非它们是内联的(通过直接在类声明中定义它们或由inline关键字指定的明确性)

头文件(主要)用于声明(无论你需要声明什么)。允许的定义是常量和内联函数/方法(以及模板)。

答案 3 :(得分:2)

实际上,可以在单个头文件中定义(没有单独的.c / .cpp文件),并且仍然可以从多个源文件中使用它。

考虑这个foobar.h标题:

#ifndef FOOBAR_H
#define FOOBAR_H

/* write declarations normally */
void foo();
void bar();

/* use conditional compilation to disable definitions when necessary */
#ifndef ONLY_DECLARATIONS
void foo() {
   /* your code goes here */
}
void bar() {
   /* your code goes here */
}
#endif /* ONLY_DECLARATIONS */
#endif /* FOOBAR_H */

如果您只在一个源文件中使用此标头,请正常包含并使用它。 就像main.c

一样
#include "foobar.h"

int main(int argc, char *argv[]) {
    foo();
}

如果您的项目中有其他源文件需要foobar.h,那么#define ONLY_DECLARATIONS宏之前包含它。 在use_bar.c中你可以写:

#define ONLY_DECLARATIONS
#include "foobar.h"

void use_bar() {
    bar();
}

编译后,use_bar.o和main.o可以无错误地链接在一起,因为只有其中一个(main.o)将实现foo()和bar()。

这有点非惯用,但它允许将定义和声明保存在一个文件中。我觉得这是一个穷人替代真实的modules

答案 4 :(得分:1)

你的第一个代码片段违反了C ++的“一个定义规则”- see here for a link to a Wikipedia article describing ODR.你实际上违反了#2点,因为每次编译器将头文件包含到源文件中时,你都遇到了编译器生成全局可见的test_ns::TestClass::testMethod()定义的风险。当然,当您链接代码时,链接器将具有小猫,因为它将在多个目标文件中找到相同的符号。

第二个片段是有效的,因为你已经内联了函数的定义,这意味着即使编译器没有为函数生成任何内联代码(比如,你已经关闭了内联或编译器决定了函数太大而不能内联),为函数定义生成的代码只能在翻译单元中看到,就像你把它放在匿名命名空间中一样。因此,您可以在生成的目标代码中获得该函数的多个副本,链接器可能会或可能不会优化,具体取决于它的智能程度。

通过在TestClass::testMethod()前添加inline前缀,您可以在第一个代码段中获得类似的效果。

答案 5 :(得分:-1)

//Baseclass.h  or  .cpp

#ifndef CDerivedclass
#include "Derivedclass.h"
#endif

or
//COthercls.h    or .cpp

#ifndef CCommonheadercls
#include "Commonheadercls.h"
#endif

I think this suffice all instances.