tmp 类是 OpenFOAM 中用来封装对象的一个类,这里将介绍 tmp 的机制及用法。

基础知识

在介绍 tmp 类之前我们有必要了解一些 C++ 的机制。在 C++ 中,当一个函数的返回值为对象时,一般情况下将执行以下过程:

  • 调用该对象的拷贝构造函数(copy constructor)构造一个临时对象;
  • 将新构造的临时对象返回;
  • 销毁原对象。

为了说明以上过程,我们来看一段简单的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

using namespace std;

struct C
{
    C() { cout << "constructor" << endl; }
    C(const C& other) { cout << "copy constructor" << endl; }
    C& operator=(const C& other) { cout << "assignment operator" << endl; }
};

C foo()
{
    C retObj;
    return retObj;
}

int main(int argc, char *argv[])
{
    C obj = foo();
    return 0;
}

用 g++ 编译,并关闭返回值优化,输出如下:

$ g++ -fno-elide-constructors main.cpp
$ ./a.out
constructor
copy constructor
copy constructor

上述代码调用了两次拷贝构造函数:第一次在函数 foo 返回时调用,用 return 返回的对象 retObj 拷贝构造了一个匿名的临时对象,并销毁 retObj ,函数返回的其实是这个匿名临时对象;第二次在函数返回后的 main 函数中调用,用临时对象拷贝构造 obj

这样做的目的是出于对内存管理安全性的考虑。然而当一个对象非常大时,在函数中返回这个对象将消耗大量的内存。如何才能避免大量内存开销呢?

tmp 类的机制

tmp 类实际上是智能指针,其功能类似 C++11 中的 shared_ptr(最新的 OpenFOAM-dev 版本中,tmp 类已经限定引用计数不能大于2)。tmp 类将大型对象封装起来,tmp 类型的对象本身只保存指向大型对象的指针或引用,因此占用内存非常小。在对 tmp 类型的对象执行拷贝构造函数时基本可以忽略其消耗的内存。

下面以 OpenFOAM-3.0.0 为例分析其源码实现,相关代码:

src/OpenFOAM/memory/tmp/tmp.H
src/OpenFOAM/memory/tmp/tmpI.H

成员变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
template<class T>
class tmp
{
    // Private data

        //- Flag for whether object is a temporary or a constant object
        bool isTmp_;

        //- Pointer to temporary object
        mutable T* ptr_;

        //- Const reference to constant object
        const T& ref_;
    ...

和 autoPtr 类相比,tmp 类的成员变量多出了 isTmp_ref_

构造函数

tmp 类可以从指针构造,也可以从常引用构造,对应以下两个构造函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
template<class T>
inline Foam::tmp<T>::tmp(T* tPtr)
:
    isTmp_(true),
    ptr_(tPtr),
    ref_(*tPtr)
{}


template<class T>
inline Foam::tmp<T>::tmp(const T& tRef)
:
    isTmp_(false),
    ptr_(0),
    ref_(tRef)
{}

若从指针构造,则说明 tmp 类管理的对象是通过 new 申请得到的,存储在堆内存(heap)上,指针 ptr_ 和引用 ref_ 同时指向被管理对象;若从常引用构造,则说明 tmp 类管理的对象不是通过 new 申请得到的,存储在栈内存(stack)上,指针 ptr_ 为空,引用 ref_ 指向被管理对象。OpenFOAM 中用的比较多的是前一种。

拷贝构造函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
template<class T>
inline Foam::tmp<T>::tmp(const tmp<T>& t)
:
    isTmp_(t.isTmp_),
    ptr_(t.ptr_),
    ref_(t.ref_)
{
    if (isTmp_)
    {
        if (ptr_)
        {
            ptr_->operator++();
        }
        else
        {
            FatalErrorIn("Foam::tmp<T>::tmp(const tmp<T>&)")
                << "attempted copy of a deallocated temporary"
            << " of type " << typeid(T).name()
            << abort(FatalError);
        }
    }
}

这里需要注意的是只有派生自 refCount 的类才能被 tmp<> 封装。拷贝构造函数中调用了 ptr_->operator++() ,实际上调用的是 refCount::operator++() , 这个函数只是在引用计数 count_ 上加一,其代码如下:

1
2
3
4
5
//- Increment the reference count
void operator++()
{
    count_++;
}

refCount 还有个函数用来判断对象是否可被销毁。当引用计数为零时,对象才能被销毁:

1
2
3
4
5
//- Return true if the reference count is zero
bool okToDelete() const
{
    return !count_;
}

这个函数在 tmp 的析构函数中被调用,如果引用计数为零才销毁被封装的对象,否则只是执行 ptr_->operator--() ,将引用计数减一:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
template<class T>
inline Foam::tmp<T>::~tmp()
{
    if (isTmp_ && ptr_)
    {
        if (ptr_->okToDelete())
        {
            delete ptr_;
            ptr_ = 0;
        }
        else
        {
            ptr_->operator--();
        }
    }
}

操作符重载

括号操作符 operator()

括号操作符返回被管理对象本身,有 const 和非 const 两个版本,代码如下:

 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
template<class T>
inline T& Foam::tmp<T>::operator()()
{
    if (isTmp_)
    {
        if (!ptr_)
        {
            FatalErrorIn("T& Foam::tmp<T>::operator()()")
                << "temporary of type " << typeid(T).name() << " deallocated"
                << abort(FatalError);
        }

        return *ptr_;
    }
    else
    {
        // Note: const is cast away!
        // Perhaps there should be two refs, one for const and one for non const
        // and if the ref is actually const then you cannot return it here.
        //
        // Another possibility would be to store a const ref and a flag to say
        // whether the tmp was constructed with a const or a non-const argument.
        //
        // eg, enum refType { POINTER = 0, REF = 1, CONSTREF = 2 };
        return const_cast<T&>(ref_);
    }
}


template<class T>
inline const T& Foam::tmp<T>::operator()() const
{
    if (isTmp_)
    {
        if (!ptr_)
        {
            FatalErrorIn("const T& Foam::tmp<T>::operator()() const")
                << "temporary of type " << typeid(T).name() << " deallocated"
                << abort(FatalError);
        }

        return *ptr_;
    }
    else
    {
        return ref_;
    }
}

tmp 类的使用

示例代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 用 tmp 封装大型类
tmp<volScalarField> tvsf = someFunction();

// 用 operator() 获得实际引用,并对其进行操作;
volScalarField& vsf = tvsf();

// 对 vsf 进行数据操作
......

// 销毁对象
tvsf.clear();