OpenFOAM-3.0 的湍流模型(四)

最后来看一个小问题: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
19
c++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) 里,详细的参考前一篇,看看这个宏函数的展开,就知道模型参数是怎么代入进去来得到实例化的湍流模型类的。