C++ 中派生类引用与基类引用的隐式转换

在读 OpenFOAM 代码过程中,有一类应用初看之下觉得很费解,比如 OpenFOAM-2.3.x 的 twoPhaseEulerFoam,createFields.H 有这么一段:

1
2
3
4
5
phaseModel& phase1 = fluid.phase1();
phaseModel& phase2 = fluid.phase2();

volScalarField& alpha1 = phase1;
volScalarField& alpha2 = phase2;

乍看之下,感觉有点奇怪:怎么能将 phaseModel 类的引用直接赋值给 volScalarField 类的引用呢?后来查看了一下 phaseModel 类的定义,发现原来 phaseModel 类是 volScalarField 类的派生,由此上面代码就好理解了,无非是将派生类引用赋值给基类引用而已。

但是,下面代码,虽然深究下去发现原理类似,但是乍看上去却更加费解:

1
2
3
4
5
6
7
8
9
10
Foam::tmp<Foam::volVectorField> Foam::twoPhaseSystem::U() const
{
return phase1_*phase1_.U() + phase2_*phase2_.U();
}
Foam::tmp<Foam::surfaceScalarField> Foam::twoPhaseSystem::calcPhi() const
{
return
fvc::interpolate(phase1_)*phase1_.phi()
+ fvc::interpolate(phase2_)*phase2_.phi();
}

这里将 phase1_ 直接与 phase1_.U()(或者 phase1_.phi())相乘,怎么理解?从原理上讲,应该是 phase1_ 的体积分率与其速度的乘积。
仔细看一下 phaseModel 类的构造函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Foam::phaseModel::phaseModel
(
const twoPhaseSystem& fluid,
const dictionary& phaseProperties,
const word& phaseName
)
:
volScalarField
(
IOobject
(
IOobject::groupName("alpha", phaseName), // "alpha.particle"
fluid.mesh().time().timeName(),
fluid.mesh(),
IOobject::READ_IF_PRESENT,
IOobject::AUTO_WRITE
),
fluid.mesh(),
dimensionedScalar("alpha", dimless, 0)
),
......
......

原来是这样,读取 “alpha.phaseName” 数据文件构建了一个 IOobject 对象,并用该对象对一个临时的 volScalarField 对象进行了初始化,然后用成员初始化列表,将该临时 volScalarField 对象对基类 volScalarField 进行初始化。

这样一来,对于上面的情形, phase1_ * phase1_.U() ,实际上是进行了一个隐式转换,先将 phase1_ 转换成基类 volScalarField 类型,由于上述 phaseModel 类的初始化设定,转换以后,phase1_ 其实就相当于用 alpha 初始化过的那个 volScalarField 类的对象了。所以, phase1_ * phase1_.U() 其实相当于 alpha.phaseName * phase1_.U() ,是两个 volScalarField 类对象之间的乘法运算。

twoPhaseEulerFoam的代码里,类似的用法还有很多,这里不能一一举例,当看到某处费解的时候,不妨想想是否是上面提到的情形。

为了便于理解这个原理,我这里写了一个简单的 c++ 测试小程序

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include<iostream>
using namespace std;

class A
{
public:
int a;
A(int a);
void print();

};
A::A(int a)
{
this->a = a;
}
void A::print()
{
cout<<"base class:"<<"a="<<a<<endl;
}

class B : public A
{
public:
int b;
B(int a, int b);
void print();
};

B::B(int a, int b):A(a)
{
this->b = b;
}
void B::print()
{
cout<<"Derived class:"<<"a="<<a<<",b="<<b<<endl;
}

void print_test( A& obja)
{

obja.print();
}

int operator*(A& a, A& b)
{
return a.a * b.a;
}

int main(int argc, char *argv[])
{

int a=2,b=4;

A obj1(a);
obj1.print();

B obj2(a+b,b);
obj2.print();

A& obj3 = obj2;
obj3.print();
cout<<"obj3.a="<<obj3.a<<endl;
// cout<<"obj3.b="<<obj3.b<<endl; Error! class A has no member named 'b'.

print_test(obj1);
print_test(obj2);

cout<<"obj1 * obj2 :"<< obj1 * obj2 << endl;

return 0;
}

输出如下:

base class:a=2
Derived class:a=6,b=4
base class:a=6
obj3.a=6
base class:a=2
base class:a=6
obj1 * obj2 :12

可以将上述程序中派生类对象赋值给基类的规律简单小结如下:

  • 当将基类的引用指向派生类的对象时,用该引用只能调用派生类从基类继承而来的成员。像上面程序中,obj3.a 输出的是 对象 obj2 的成员 a,但是 obj3 无法调用成员b。
  • 当一个函数需要的参数是基类的引用时,可以直接将派生类的对象传递给该函数,像上面的 print_test 函数和重载的 * 运算符那样。此事相当于作了一个隐式的将派生类对象赋值给基类引用。