深入探索C++对象模型之七 --- 站在对象模型的尖端

深入探索C++对象模型之七 — 站在对象模型的尖端

template instantiation 模版具现化
template、exception handling和runtime type idetification

Template

模版只是定义了函数

tempalte的声明

当我们声明template的时候,编译器对于声明代码是不做任何处理的,只有在程序中使用到了具体类型的类或者函数的时候,编译器才会根据模版定义具现化该类型参数的class和member function。编译器会根据出现的参数类型instantiation一个具体的实体。

  1. 如果在程序中使用到模版类中的static data member。必须指定patter,如:

    template T>
    class Point {
        ...
    private:
        static int value;
    }

    我们如果想访问value的话必须Point::value或者Point::value, 而不能是Point::value.这会使得编译器产生针对int或者double的instantiation实体。否则的话编译器不做任何处理自然也不存在所谓的static data member实体。
    其次,我们如果使用point的话是不会产生instantiation实体的,如Point *ptr=0,这的确是一个Point指针,但是这个不是一个Point class object,不关心该object的内部布局。而使用refenece的话则必须要instantiation实体,因为reference是class object的引用,因此必须要产生instantiation实体。

  2. member function也不应该全部被“实体”化,只有在member functions被使用的时候才会被instantiation。试想如果class中有100个member functions,但是只用到了两三个,那么如果实体化全部的函数将会花费大量的时间和空间。

  3. 那么这些member function是在什么时候“具现”出来的呢?要么在编译时期,要么则是在链接时期。

  4. 所有与类型有关的校验,如果牵涉到template参数都必须延迟到真正的具现操作时才发生。

  5. Template中的名称决议方式。一个编译器必须保持两个scope context
    5.1 scope of template declaration 用于专注于一般的template class。当名称的决议和template参数类型无关的时候就使用该scope
    5.2 scope of template instantiation 用于专注于特定的实体。当名称的决议和template参数类型相关的时候则使用该scope

  6. Member function的具现行为

template的特化和偏特化(template specialization and partial specialization)

对于之前提到的template instantiation,在编译时期编译器可以根据你传入的模版参数instantiation实体,并且具现化class member function。但是有时候真对某些类型可能member function的具体行为我们需要重新定义,那么这时候就需要额外限定模版的参数,指定当这种参数类型的时候使用自己特别定制的member function,这就是特化。

而如果template我们只是指定部分的类型那么这就是偏特化。

如下例子:

#include
using namespace std;

class man {
    private:
        string name;
        int data;
    public:
        man(string s, int i):name(s),data(i) {
        }
        void show() const {
            cout << "this name is " << name << ", data = " << data << endl;
        }
        void setData(int d) {
            data = d;
        }
        int getData() const {
            return data;
        }
};

template <class T>
void mSwap(T &t1, T &t2) {
    T temp = t1;
    t1 = t2;
    t2 = temp;
    cout << "Here are template version " << t1 << "and" << t2 << endl;
};

int main() {
    man m1("aa", 99);
    man m2("bb", 100);
    mSwap(m1, m2);
}

上面的代码编译失败是因为编译器会instantiation一个man的实体,但是man类型没有定义<<运算符,因此在mSwap最后一行就会编译报错,因此我们就需要针对man这种特别定制mSwap函数,如下:

tempalte <>
void mSwap(man &m1, man &m2) {
    /*do something*/
}

如果有非模板函数存在,当匹配度相同的时候优先使用非模板函数。函数模板定制是实例化模板,而不是模板的重载。

异常处理

c++程序中exception handling主要有三个语汇构成:(1)一个thow子句 (2)一个或者多个catch子句 (3)一个try区段

当一个exception被丢出去时,控制权会从函数调用中释放出来,并寻找一个吻合的catch子句,如果找不到的话就使用默认的处理例程terminate().当控制权被放弃后,堆栈中的每一个函数调用都将被推离,这个程序称为unwinding the stack。在每一个函数被推离堆栈之前,函数的local class object的destructor会被调用。

对于在函数内部各个区段间产生的exception,到底是否已经产生local class object以及是否需要释放资源这些都是编译器干的事。

对于每一个被丢出来的exception,编译器都必须产生一个类型描述器,同时编译器还要为每一个catch子句产生一个类型描述器。执行期间exception handle就将这两个描述器进行比较直到找到一个吻合的catch子句处理,或者直到堆栈已经被“unwound”而terminate()已被调用。

如果在catch语句段中再次throw出exception object的话,抛出的仍然是原来一开始的object class,即使在catch中做过处理,catch子句对于exception object的任何改变都是局部性的。

执行期类型识别 RTTI

dynamic_cast可以在执行期决定真正的类型,如果downcast是安全的那么这个运算符会传回适当转型过的指针,而如果downcast是不安全的那么这个运算符传回0。那么dynamic_cast的成本是什么呢?

type_info是C++所定义的类型描述器的class名称,该class中放置着需要的类型信息。virtual table中第一个slot内含type_info object的地址。当需要判断时,这两个比较器会被交给一个runtime library函数比较,告诉我们是否吻合。

程序执行中对一个class指针类型施以dynamic_cast运算符的话,会获得true或false

  • 如果传回真正的地址,表示这个object的动态类型被确认了,一些类型有关的操作可以施行于其上
  • 如果传回0,表示没有指向任何object,意味着应该以另外一种逻辑施行于这个动态类型未确定的object上

而如果对一个reference施行于dynamic_cast运算符的话,会是另外一个结果:

  • 如果reference参考适当的derived class,那么downcast会被执行而程序可以继续进行
  • 如果reference并不真正是一种derived class,那么会丢出一个bad_cast exception。

typeid运算符传回一个const reference,类型是type_info。

你可能感兴趣的:(C++)