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

Pao*_*sco 26 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
Run Code Online (Sandbox Code Playgroud)

如果在同一个项目中我从多个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
Run Code Online (Sandbox Code Playgroud)

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

wor*_*ad3 22

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

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

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

#ifndef EXAMPLE_H
#define EXAMPLE_H

//define your class here

#endif
Run Code Online (Sandbox Code Playgroud)


QBz*_*ziZ 21

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

namespace test_ns{

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

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

} // end namespace test_ns
Run Code Online (Sandbox Code Playgroud)

编辑

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

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


sas*_*nin 5

实际上,可以在单个头文件中进行定义(无需单独的 .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 */
Run Code Online (Sandbox Code Playgroud)

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

#include "foobar.h"

int main(int argc, char *argv[]) {
    foo();
}
Run Code Online (Sandbox Code Playgroud)

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

#define ONLY_DECLARATIONS
#include "foobar.h"

void use_bar() {
    bar();
}
Run Code Online (Sandbox Code Playgroud)

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

这有点不习惯,但它允许将定义和声明放在一个文件中。我觉得它是真正模块的穷人的替代品。