1. 程式人生 > 實用技巧 >C++ 介面,RTTI的簡單介紹

C++ 介面,RTTI的簡單介紹

1.C++介面

先要說明的是,C++的關鍵字中並沒有interface,但java和C#中有interface關鍵字,即介面。interface和class不同,interface僅有介面宣告,而且所有的宣告預設的訪問許可權是public而非private(是不是想到了C++中的struct?)。

對於C++來說,這相當於抽象類的概念,即其中的成員函式都是純虛擬函式,只有宣告,沒有實現。如:

class abstractClass{
virtual memfunc1() = 0;
virtual memfucn2() = 0;
};

這是一個用於實現介面的純抽象類,僅包括純虛擬函式的類(一般用作基類,派生類進行具體的實現)。純虛擬函式是指用=0標記的虛擬函式。

抽象類是不能例項化的,換句話說,它只是提供一個interface的功能,它並不實現這些純虛擬函式。正如第一段中所講,我們可以用C++中的struct來模擬interface,可以採用兩種方式:

  1. 採用巨集定義:#define interface struct;
  2. 使用typedef: typedef struct interface。

這樣就可以在C++中使用interface了。

1、介面類中不應該宣告成員變數,靜態變數。

2、可以宣告靜態常量作為介面的返回值狀態,需要在對應的cpp中定義並初始化,訪問時需要使用"介面型別::靜態常量名"訪問

2、定義的介面方法使用virtual 修飾符 和 “=0” 修飾,表示該方法是純虛的。

3、因為介面類是無法建立物件的,所以不應該編寫建構函式和解構函式

2.RTTI

RTTI概念

RTTI(Run Time Type Identification)即通過執行時型別識別,程式能夠使用基類的指標或引用來檢查著這些指標或引用所指的物件的實際派生型別。

RTTI機制的產生

為什麼會出現RTTI這一機制,這和C++語言本身有關係。和很多其他語言一樣,C++是一種靜態型別語言。其資料型別是在編譯期就確定的,不能在執行時更改。然而由於面向物件程式設計中多型性的要求,C++中的指標或引用(Reference)本身的型別,可能與它實際代表(指向或引用)的型別並不一致。有時我們需要將一個多型指標轉換為其實際指向物件的型別,就需要知道執行時的型別資訊,這就產生了執行時型別識別的要求。和Java相比,C++要想獲得執行時型別資訊,只能通過RTTI機制,並且C++最終生成的程式碼是直接與機器相關的。

我對Java的執行時型別識別不是很熟悉,所以查了一下相關資料:Java中任何一個類都可以通過反射機制來獲取類的基本資訊(介面、父類、方法、屬性、Annotation等),而且Java中還提供了一個關鍵字,可以在執行時判斷一個類是不是另一個類的子類或者是該類的物件,Java可以生成位元組碼檔案,再由JVM(Java虛擬機器)載入執行,位元組碼檔案中可以含有類的資訊。

typeid和dynamic_cast操作符

RTTI提供了兩個非常有用的操作符:typeid和dynamic_cast。

typeid操作符,返回指標和引用所指的實際型別;

dynamic_cast操作符,將基類型別的指標或引用安全地轉換為其派生類型別的指標或引用。

我們知道C++的多型性(執行時)是由虛擬函式實現的,對於多型性的物件,無法在程式編譯階段確定物件的型別。當類中含有虛擬函式時,其基類的指標就可以指向任何派生類的物件,這時就有可能不知道基類指標到底指向的是哪個物件的情況,型別的確定要在執行時利用執行時型別標識做出。為了獲得一個物件的型別可以使用typeid函式,該函式反回一個對type_info類物件的引用,要使用typeid必須使用標頭檔案<typeinfo>,因為typeid是一個返回型別為typ_info的引用的函式所以這裡有必要先介紹一下type_info類。

對於原始碼可以簡單解釋為:

class type_info {  
public:  
        //解構函式  
    _CRTIMP virtual ~type_info();  
    //過載的==操作符  
    _CRTIMP int operator==(const type_info& rhs) const;  
    //過載的!=操作符  
    _CRTIMP int operator!=(const type_info& rhs) const;  
    _CRTIMP int before(const type_info& rhs) const;//用於type_info物件之間的排序演算法  
    //返回類的名字  
    _CRTIMP const char* name() const;  
    _CRTIMP const char* raw_name() const;//返回類名稱的編碼字串  
private:  
    //各種儲存資料成員  
    void *_m_data;  
    char _m_d_name[1];  
    //將拷貝建構函式與賦值建構函式設為了私有  
    type_info(const type_info& rhs);  
    type_info& operator=(const type_info& rhs);  
};  

因為type_info類的複製建構函式和賦值運算子都是私有的,所以不允許使用者自已建立type_info的類。唯一要使用type_info類的方法就是使用typeid函式。

typeid函式

typeid函式的主要作用就是讓使用者知道當前的變數是什麼型別的,比如使用typeid(a).name()就能知道變數a是什麼型別的。typeid()函式的返回型別為typeinfo型別的引用。

typeid函式是type_info類的一個引用物件,可以訪問type_info類的成員。但因為不能建立type_info類的物件,而typeid又必須反回一個型別為type_info型別的物件的引用,所以怎樣在typeid函式中建立一個type_info類的物件以便讓函式反回type_info類物件的引用就成了問題。這可能是把typid函式宣告為了type_info類的友元函式來實現的,預設建構函式並不能阻止該類的友元函式建立該類的物件。所以typeid函式如果是友元的話就可以訪問type_info類的私有成員,從而可以建立type_info類的物件,從而可以建立返回型別為type_info類的引用。

例如:

class A{
private:
A(){}
A(const A&){}
A& operator = (const A&){}
friend A& f(); 
};

函式f()是類A的友元,所以在函式f()中可以建立類A的物件。同時為了實現函式f()反回的物件型別是A的引用,就必須在函式f中建立一個類A的物件以作為函式f的反回值,比如函式f可以這樣定義:

A &f()
{
A m_a;
return m_a;
}

因為typeid函式是type_info類的物件,也就是說可以用該函式訪問type_info類的成員,即type_info類中過載的==和!=運算子,name()和before()成員函式,比如typid(a).name()和typid(a)==typid(b)等等。

class A{
private:
    A(){ b = 3; cout << "A\n"; }
public:
    void name()
    {
        cout << "Class Name is A\n";
    }
    friend A &f();
private:
    int b;
};
A &f()
{
    A friend_A;
    cout << "The function of Class A\n";
    return friend_A;
}
int main()
{
    f().name();
    return 0;
}

執行截圖:

函式f()是類A的友元,且返回一個類A的物件,因為f()函式是類A的友元,所以在函式f中可以用預設建構函式建立類A的物件,這時函式f()同時是一個函式,也是類A的物件,因此也可以訪問類A中的成員。

typeid函式的使用示例:

class A{
private:
    int a;
};

class B :public A{
public:
    virtual void f(){ cout << "HelloWorld\n"; }
private:
    int b;
};

class C :public B{
public:
    virtual void f(){ cout << "HelloWorld++\n"; }
private:
    int c;
};

class D :public A{
public:
    virtual void f(){ cout << "HelloWorld--\n"; }
private:
    int d;
};
int main()
{
    int a = 2;
    cout << typeid(a).name() << endl;
    A objA;
    //打印出class A  
    cout << typeid(objA).name() << endl;
    B objB;
    //打印出class B  
    cout << typeid(objB).name() << endl;
    C objC;
    //打印出class C  
    cout << typeid(objC).name() << endl;
    
    //以下是多型在VC 6.0編譯器不支援,但是在GCC以及微軟更高版本的編譯器卻都是
    //支援的,且是在執行時候來確定型別的,而不是在編譯器,會打印出class c
    B *ptrB=new C();
    cout<<typeid(*ptrB).name()<<endl;
    
    A *ptrA = new D();
    //打印出class A而不是class D  
    cout << typeid(*ptrA).name() << endl;
    return 0;
}

執行截圖:

dynamic_cast強制轉換運算子

該轉換符用於將一個指向派生類的基類指標或引用轉換為派生類的指標或引用,注意dynamic_cast轉換符只能用於含有虛擬函式的類,其表示式為dynamic_cast<型別>(表示式),其中的型別是指把表示式要轉換成的目標型別,比如含有虛擬函式的基類B和從基類B派生出的派生類D,則B *pb; D *pd, md; pb=&md; pd=dynamic<D*>(pb); 最後一條語句表示把指向派生類D的基類指標pb轉換為派生類D的指標,然後將這個指標賦給派生類D的指標pd,有人可能會覺得這樣做沒有意義,既然指標pd要指向派生類為什麼不pd=&md;這樣做更直接呢?

因為有些時候我們需要強制轉換,比如如果指向派生類的基類指標B想訪問派生類D中的除虛擬函式之外的成員時就需要把該指標轉換為指向派生類D的指標,以達到訪問派生類D中特有的成員的目的,比如派生類D中含有特有的成員函式g(),這時可以這樣來訪問該成員dynamic_cast<D*>(pb)->g();因為dynamic_cast轉換後的結果是一個指向派生類的指標,所以可以這樣訪問派生類中特有的成員。但是該語句不影響原來的指標的型別,即基類指標pb仍然是指向基類B的。

dynamic_cast轉換符只能用於指標或者引用。dynamic_cast轉換符只能用於含有虛擬函式的類。dynamic_cast轉換操作符在執行型別轉換時首先將檢查能否成功轉換,如果能成功轉換則轉換之,如果轉換失敗,如果是指標則反回一個0值,如果是轉換的是引用,則丟擲一個bad_cast異常,所以在使用dynamic_cast轉換之間應使用if語句對其轉換成功與否進行測試,比如pd=dynamic_cast<D*>(pb); if(pd){…}else{…},或者這樣測試if(dynamic_cast<D*>(pb)){…}else{…}。

對於其它的強制轉換運算子:static_cast,reinterpret_cast,const_cast,請閱讀C++中“強制轉換”的四大天王