OpenFOAM 中的类基本都遵循类的声明和定义分开在不同文件的规则。具体来说,一般是类的声明放在 “xxx.H”,类的成员函数的具体定义 “xxx.C”,如果有内联函数(inline),则还有 “xxxI.H”,并且,”xxx.H” 文件的最后会有 #include "xxxI.H"
。这么做不仅是一种代码规范,真正的目的应该是为了防止重复定义的问题。本篇博文用一个简单的例子来说明这个问题。
为了防止混淆,先说明一下概念:
- 函数声明:即只声明函数的返回类型,函数名以及参数列表,没有函数体,不具体定义函数的功能,比如
1 | int max(int a, int b); |
- 函数定义:包含函数体,具体定义函数的功能。比如
1 | int max(int a, int b) |
下面用一个简单的例子来说明。
测试一:非内联函数声明和定义必须分开的原因是防止重复定义问题
正常测试:
以下的测试代码共包括5个源文件,inlinetest.H
, inlinetest.cpp
, inlinederived.H
, inlinederived.cpp
, main.cpp
,分别定义如下:
inlinetest.H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#ifndef _INLINETEST_H
#define _INLINETEST_H
#include <iostream>
using namespace std;
class Itest //基类
{
public:
Itest(int a, int b); // 构造函数
int a_, b_; //数据成员
inline int a(); // 内联成员函数
int b(); //非内联成员函数
};
inline int Itest::a() //内联函数的定义
{
cout << "a=" << a_ << endl;
return a_;
}
#endifinlinetest.cpp
1
2
3
4
5
6
7
8
9
10#include "inlinetest.H"
Itest::Itest(int a, int b):a_(a),b_(b)
{
}
int Itest::b()//非内联函数的定义
{
cout << "b=" << b_ << endl;
return b_;
}inlinederived.H
1
2
3
4
5
6
7
8
9
10
11
12
13
14#ifndef _INLINEDERIVED_H
#define _INLINEDERIVED_H
#include <iostream>
#include "inlinetest.H"
using namespace std;
class Iderive: public Itest //派生类
{
public:
int c_;
int c();//非内联成员函数
Iderive(int a, int b, int c);
};
#endifinlinederived.cpp
1
2
3
4
5
6
7
8
9#include "inlinederived.H"
Iderive::Iderive(int a, int b, int c):Itest(a,b),c_(c)
{}
int Iderive::c()
{
cout << "c=" << c_ << endl;
return c_;
}main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18#include <iostream>
#include "inlinetest.H"
#include "inlinederived.H"
int main(int argc, char *argv[])
{
Itest obj1(2,3); //基类对象
Iderive obj2(1,2,3); // 派生类对象
obj1.a();
obj1.b();
obj2.a();
obj2.b();
obj2.c();
return 0;
}
以上五个源文件是可以成功编译链接成可执行文件的,运行结果与预期一致:1
2
3
4
5a=2
b=3
a=1
b=2
c=3
异常测试:
下面来修改,如果将基类代码改一下,将非内联函数 b
的定义也放到头文件里,即:
inlinetest.H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26#ifndef _INLINETEST_H
#define _INLINETEST_H
#include <iostream>
using namespace std;
class Itest //基类
{
public:
Itest(int a, int b); // 构造函数
int a_, b_; //数据成员
inline int a(); // 内联成员函数
int b(); //非内联成员函数
};
inline int Itest::a() //内联函数的定义
{
cout << "a=" << a_ << endl;
return a_;
}
int Itest::b()//非内联函数的定义
{
cout << "b=" << b_ << endl;
return b_;
}
#endifinlinetest.cpp
1
2
3
4
5
6
7
8
9
10#include "inlinetest.H"
Itest::Itest(int a, int b):a_(a),b_(b)
{
}
//int Itest::b()//非内联函数的定义
//{
// cout << "b=" << b_ << endl;
// return b_;
//}
结果是无法通过编译:1
2
3
4
5
6
7
8
9
10g++ -c inlinetest.cpp
g++ -c inlinederived.cpp
g++ -o main main.o inlinetest.o inlinederived.o
inlinetest.o:inlinetest.cpp:(.text+0x0): multiple definition of `Itest::b()'
main.o:main.cpp:(.text+0x0): first defined here
inlinederived.o:inlinederived.cpp:(.text+0x0): multiple definition of `Itest::b()'
main.o:main.cpp:(.text+0x0): first defined here
c:/mingw/bin/../lib/gcc/mingw32/4.8.1/../../../../mingw32/bin/ld.exe: main.o: bad reloc address 0x0 in section `.ctors'
collect2.exe: error: ld returned 1 exit status
make: *** [main] Error 1
可见,编译过程没有出错,但是链接时出错了,报错说 Itest::b()
函数(即基类中的非内联成员函数)被重复定义。原因在于,main.cpp
里有 #include "inlinetest.H"
,这句包含了对Itest::b()
函数的定义;此外,main.cpp
里还有 #include "inlinederived.H"
,而由于 inlinederived.H
里,也包含了#include "inlinetest.H"
。所以,相当于main.cpp
中也对 Itest::b()
函数的定义了两次,于是连接过程就报错了。
在上面可以正常编译的情况里,即将Itest::b()
函数放在 inlinetest.cpp
里,就不会有这个问题,因为这时头文件里只有函数的声明,而函数的声明是可以重复的。
有人可能会问,这里的 main.cpp
里完全可以去掉 #include "inlinetest.H"
,这样也可以解决重复定义问题。对这里的简单例子,是没问题,但是,如果是像 OpenFOAM 这样大的项目,源文件之间的关系非常复杂,那就只能通过将声明和定义分离来解决这个问题了。
注意:这里的内联成员函数 a
,声明和定义都在头文件 inlinetest.H
里,但是却不会报重复定义的错误。
测试二:内联函数的声明和定义需要在同一个文件里,否则无法通过编译。
将上面提到的派生类代码修改一下,将内联成员函数的定义放到 inlinetest.cpp
里,即
inlinetest.H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#ifndef _INLINETEST_H
#define _INLINETEST_H
#include <iostream>
using namespace std;
class Itest //基类
{
public:
Itest(int a, int b); // 构造函数
int a_, b_; //数据成员
inline int a(); // 内联成员函数
int b(); //非内联成员函数
};
//inline int Itest::a() //内联函数的定义
//{
//cout << "a=" << a_ << endl;
//return a_;
//}
#endifinlinetest.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#include "inlinetest.H"
Itest::Itest(int a, int b):a_(a),b_(b)
{
}
inline int Itest::a() //内联函数的定义
{
cout << "a=" << a_ << endl;
return a_;
}
int Itest::b()//非内联函数的定义
{
cout << "b=" << b_ << endl;
return b_;
}
这时无法通过编译,编译器报错如下:1
2
3
4
5
6
7
8
9
10
11
12
13g++ -c main.cpp
In file included from main.cpp:2:0:
inlinetest.H:13:13: warning: inline function 'int Itest::a()' used but never defined [enabled by default]
inline int a();
^
g++ -c inlinetest.cpp
g++ -c inlinederived.cpp
g++ -o main main.o inlinetest.o inlinederived.o
main.o:main.cpp:(.text+0x5c): undefined reference to `Itest::a()'
main.o:main.cpp:(.text+0x70): undefined reference to `Itest::a()'
c:/mingw/bin/../lib/gcc/mingw32/4.8.1/../../../../mingw32/bin/ld.exe: main.o: bad reloc address 0x0 in section `.ctors'
collect2.exe: error: ld returned 1 exit status
make: *** [main] Error 1
首先是编译过程有一个警告说 inlinetest.H
里的函数 int Itest::a()
没有定义,然后链接的时候报了 undefined reference to 'Itest::a()'
的错误,说明如果内联函数的声明和定义分属不同源文件,编译器是无法找到函数的定义的。
测试三 :内联函数的定义放到一个单独的源文件,并在头文件里 include 这个源文件
这个测试我做的比较简单,即将 inlinetest.H
拆开,内联函数 a
的定义放在单独的一个 inlinetestI.H
里:
inlinetest.H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#ifndef _INLINETEST_H
#define _INLINETEST_H
#include <iostream>
using namespace std;
class Itest //基类
{
public:
Itest(int a, int b); // 构造函数
int a_, b_; //数据成员
inline int a(); // 内联成员函数
int b(); //非内联成员函数
};
#include "inlinetestI.H"
#endifinlinetestI.H
1
2
3
4
5inline int Itest::a() //内联函数的定义
{
cout << "a=" << a_ << endl;
return a_;
}
编译链接成功,运行结果跟正常预期一致。
经过上述简单的测试,可以得到以下结论:
- 使用C++模板编程时,对于非内联成员函数,函数声明和定义要分开,目的在于防止重复定义的问题。
- 内联函数的声明和定义需要在同一个文件里,否则无法通过编译。
- OpenFOAM 里将内联成员函数提取出来放到一个单独的文件里(
*I.H
),应该只是一种使用惯例。不是 C++ 语法的要求,将*I.H
里的内容拷贝出来放到*.H
后面,然后删除#include "*I.H"
效果应该是一样的。