運行時類型識別


前言

我們知道,在類的繼承中可以通過基類的指針(或引用)指向派生類的對象,利用多態性執行派生類中提供的功能。但這僅限與調用基類中聲明的虛函數。如果希望對於一部分派生類的對象,調用派生類中引入的新函數,則無法通過基類指針進行。我們可以通過static_cast,將基類指針轉換成派生類指針。但這樣做並不安全,只能在指針所指向的對象類型明確的情況下執行。而有時只有在運行時才能知道指針所指對象的實際類型是聲明。C++中提供了兩種運行時類型識別的機制,下面分別介紹。

1、dynamic_cast

dynamic_cast是與static_cast,const_cast,reinterpret_cast並列的4中類型轉換操作符之一。它可以將基類的指針顯示轉換成派生類的指針(引用也一樣),並在轉換時會檢查指針所指向的對象的實際類型是否與轉換的目的類型兼容,如果兼容轉換才會發生,否則:

  • 如果執行的是指針類型的轉換,會得到空指針
  • 如果執行的是引用類型的轉換,會拋出異常。
    另外,轉換前類型必須是指向多態類型的指針,或多態類型的引用,這是因為C++只為多態類型在運行時保存用於運行時類型識別的信息。這也從另一個方面說明為什么非多態類型不宜被公有繼承。

示例

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void fun1() {
        cout << "Base::fun1()" << endl;
    }
};

class Base1 : public Base
{
public:
    void fun1() {
        cout << "Base1::fun1()" << endl;
    }
    virtual void fun2() {
        cout << "Base1::fun2()" << endl;
    }
};

class Base2 : public Base1
{
public:
    void fun1() {
        cout << "Base2::fun1()" << endl;
    }
    void fun2() {
        cout << "Base2::fun2()" << endl;
    }
};

void fun(Base *b)
{
    b->fun1();
    Base1 *d = dynamic_cast<Base1 *> (b);
    if (d)
        d->fun2();
}

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

    Base b;
    fun(&b);
    Base1 d1;
    fun(&d1);
    Base2 d2;
    fun(&d2);

    return 0;
}

/* *Base::fun1() *Base1::fun1() *Base1::fun2() *Base2::fun1() *Base2::fun2() */

由於fun1函數是基類中定義的函數,通過Base類的指針b可以直接調用fun1函數,fun2函數是派生類Base1中引入的新函數,只能對Base1和Base2類的對象調用。因此在fun函數中,將Base指針b轉換成Base1類指針d。並根據轉換結果是否成功,來決定是否調用fun2函數。

2、typeid

typeid是C++中的一個關鍵字,用它可以獲得一個類型的相關信息。語法形式如下:

typeid (表達式)
//or
typeid (類型說明符)

typeid既可以作用於一個表達式,也可以作用於一個類型的說明符。例如:

typeid(5+3)     //將typeid作用於一個表達式
typeid(int)     //將typeid作用於一個類型說明符

1通過typeid得到一個type_info類型的常引用。type_info是C++標准庫中的一個類,用於在運行時表示類型信息,它的定義在typeinfo頭文件中。type_info類有一個名為name的函數,用來獲取類型的名稱。原型如下:

const char* name() const

如果typeid所作用於的表達式具有多態類型,那么這個表達式會被求值,用typeid得到的是用於描述表達式求值結果的運行時類型(動態類型)的type_info對象的常引用。而如果表達式具有非多態類型,那么用typeid得到的表達式靜態類型,由於之歌靜態類型在編譯時就能確定,這是表達式不會被求值。
示例

#include <iostream>
#include <typeinfo>
using namespace std;

class Base
{
public:
    virtual void fun1() {
        cout << "Base::fun1()" << endl;
    }
};

class Base1 : public Base
{
public:
    void fun1() {
        cout << "Base1::fun1()" << endl;
    }
    virtual void fun2() {
        cout << "Base1::fun2()" << endl;
    }
};

void fun(Base *b)
{
    const type_info &info1 = typeid(b);
    const type_info &info2 = typeid(*b);

    cout << "typeid(b): " << info1.name() << endl;
    cout << "typeid(*b): " << info2.name() << endl;
    if (info2 == typeid(Base)) {
        cout << "A Base class" << endl;
    }
}

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

    Base b;
    fun(&b);
    Base1 d1;
    fun(&d1);

    return 0;
}

輸出結果為:

typeid(b): P4Base
typeid(*b): 4Base
A Base class
typeid(b): P4Base
typeid(*b): 5Base1

在函數fun中,由於b的類型是Base ,而Base是多態類型,所以用typeid(*b)得到的是b指針所指向的對象的具體類型,因此兩次調用fun函數所得到的結果不同。雖然b是指向多態類型的指針,但其自身確實Base 一個指針類型,而不是多態類型,所以兩次調用結果相同。

注意
C++標准中並沒有明確規定type_info對象的name()成員函數所返回字符串的構造方式,因此不同編譯器可能的實現會有所不同。運行時類型識別機制雖然靈活,但是卻要付出一定的效率代價,因此不同把它視為常規方法。多數情況下,派生類的特殊性是可以通過在基類中定義虛函數加以體現的,運行時類型檢查只是一種輔助性手段,在必要時才使用。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com