我有一个包含类的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
由于类是在命名空间内定义的,这两种形式不应该是等价的吗?为什么在第一种情况下认为方法定义了两次?
答案 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.