最后来看一个小问题:OpenFOAM-3.0 中的湍流模型是怎么编译的。在这之前,湍流模型的编译很直观,将需要编译的湍流模型的代码的 .C
文件写到 Make/files
里就好了。可是,在 OpenFOAM-3.0 里,很多湍流模型代码的 .C
文件并没有写到 Make/files
里,而是在 makeTurbulenceModels.C
里类似这样写1
2#include "mykEpsilon.H"
makeRASModel(mykEpsilon);
然后在 Make/files
里写的是这个 makeTurbulenceModels.C
文件。为什么呢?
这里来分析一下这个问题。
这个问题,说起来也简单,不过我经过很多摸索从想明白。其实,这个问题用一个概念就可以解释清楚:条件编译。
条件编译常用于头文件,格式如下1
2
3
4
5
6
7//filename: kEpsilon.H
#ifndef kEpsilon_H
#define kEpsilon_H
......
......
......
#endif
这样能避免头文件重复引用的导致变量或者类重复定义的问题,原因在于,第一次 #include "kEpsilon.H"
时,会触发 #define kEpsilon_H
的操作,之后如果代码中再出现 #include "kEpsilon.H"
则 #ifndef kEpsilon_H
将不再成立,所以, #ifndef ... #endif
之间的内容将不会再度被引入。
OpenFOAM 的模板类中,还常见的一种用法是1
2
3#ifdef NoRepository
# include "kEpsilon.C"
#endif
这个怎么理解呢? 本来也不难理解,无非就是如果定义了宏 NoRepository
则在头文件里将类的具体定义部分也引入到头文件里。问题是,翻遍 OpenFOAM 的 src
目录下的源码,也找不到 NoRepository
的定义。经过搜索,原来这个 NoRepository
是通过 g++ 的 -D
选项来定义的!在编译过程中,使用1
g++ -c -DNoRepository ...
相当于进行了 #define NoRepository 1
的操作,所以, #ifdef NoRepository
这个条件是满足的。
OpenFOAM 中,编译选项的定义在 wmake/rules
下,具体的定义取决于你用的编译器,以 linux64Gcc/c++
为例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19c++WARN = -Wall -Wextra -Wno-unused-parameter -Wold-style-cast -Wnon-virtual-dtor
CC = g++ -m64
include $(RULES)/c++$(WM_COMPILE_OPTION)
ptFLAGS = -DNoRepository -ftemplate-depth-100
c++FLAGS = $(GFLAGS) $(c++WARN) $(c++OPT) $(c++DBUG) $(ptFLAGS) $(LIB_HEADER_DIRS) -fPIC
Ctoo = $(WM_SCHEDULER) $(CC) $(c++FLAGS) -c $$SOURCE -o $@
cxxtoo = $(Ctoo)
cctoo = $(Ctoo)
cpptoo = $(Ctoo)
LINK_LIBS = $(c++DBUG)
LINKLIBSO = $(CC) $(c++FLAGS) -shared -Xlinker --add-needed -Xlinker --no-as-needed
LINKEXE = $(CC) $(c++FLAGS) -Xlinker --add-needed -Xlinker --no-as-needed
可见这里的确是使用了 -DNoRepository
选项。
为什么要在头文件里 #include "kEpsilon.C"
呢?这就涉及到模板类的实例化的问题。这篇文章详细地探讨了模板类实例化过程中会遇到的问题,以及解决的办法。简单地说,模板类不是一种类型,而是一种模板,模板类通过代入模板参数来实例化,以得到具体的类(这个具体的类就可以看作是一种“数据类型”了)。在示例化过程中,编译器不光需要模板类的声明部分,还需要知道模板类的成员函数的具体定义部分,所以,如果在进行模板类声明的地方只包含了模板类的声明部分(一般是头文件),那实例化就会失败,编译器会报类似“undefined reference to …” 的错误。
cfd-online 上的这个帖子也探讨了关于 NoRepository
的问题。
最后,makeTurbulenceModels.C
里的 #include "kEpsilon.H"
,其实相当于也 #include "kEpsilon.C"
,而且,要注意这里 include 进来的是一个模板类,是用来建立实例化的模型的!实例化的过程,在 makeRASModel(kEpsilon)
里,详细的参考前一篇,看看这个宏函数的展开,就知道模型参数是怎么代入进去来得到实例化的湍流模型类的。