2017年3月22日 星期三

虛擬函式的實際用途 與 上下選型的概念

虛擬函式的實際用途 與 繼承時上下轉型的概念

tags: C++ Concept
  1. 敘述什麼時候必須、一定要使用虛擬函式,以及他真正的用途
  2. 敘述什麼是上下轉型及說明dynamic_cast的操作時機以及他與 static_cast 有什麼不同

虛擬函式

虛擬函式並不是,能夠讓函式被繼承這麼簡單,這是從答案反推的單向結果論

檢視

正確的形容方式應該說成,在以指針檢視時,能夠依據正確的 類型 自動套用正確的函式使用

選擇

virtual 可以把他當作可有可無的低優先權命令,當編譯器使用指標檢視遇到
目前儲存型態實際型態 時自動選擇實際的型態函式

示例

這麼說其實還是有點不好理解,實際由以下代碼來說明
代碼結構
class A {
public:
    A(){}
    void virtual fun(){
        cout << "this is A" << endl;
    }
};

class B: public A{
public:
    B(){}
    void fun(){
        cout << "this is B" << endl;
    }
    int i=1;
};

當我們使用指針檢視(操作)時
A *a, *b;

a = new A;
b = new B;
這裡的 A 我把它稱為 目前儲存型態 ,也就是我們告訴編譯器,會已 A 的角度(型態),來檢視(操作)這個指針
可注意到 b 它的實際型態應該是 B 但是被強迫只用 A 型態檢視,這裡稱為向上轉型,也就是 b 的實際型態應該為 B

(*a).fun();
目前 a 的檢視型態為 A 所以可以正確地呼叫 A 裡面的成員函式,這很容易理解並沒有什麼問題。

(*b).fun();
這裡就有問題了,b 的實際型態是B,可是卻被宣告用 A 的角度來檢視,一般來說會直接呼叫 A 的函式成員
但如果在 A 的資料函式加上 Virtual ,將可以自動選擇實際的 B 函式

上下轉型

在本範例中 A 稱為父類別 B 稱為子類別
  • 向上轉型:由子類別轉型為父類別(或再上一層)
  • 向下轉型:由父類別轉型為子類別(或再下一層)
他們之間的關係具有以下特性
  • B 繼承了 A 的所有資源,A 有東西 B 也一定有
  • B 新增的資料成員與函式 A 不會擁有
依據剛剛的 指針檢視行為 可以把想像成是我要檢視到哪裡。
(註:只是我自己想到的一個類比說明方法)
比如說的資料的地址長度可能是這樣
  單位 1    單位2
---------------------
  A 地址  | xxxxxxx
---------------------
  B    地      址
---------------------
          |    i
---------------------
可以看到 B 的角度檢視的範圍比較長一些,如果你用 A 的角度檢視,就無法見到變數 i 。
由此可見,你要將任何子類別向上轉型成父類別都不會出狀況的,只不過會無法存取(不會丟失),所有自己額外擁有的成員。

向下轉型可能造成非法存取

但是如果你要做向下轉型就會出問題的,如果他當初建立的時候就是用父型態建立,你將檢視到沒有被初始的垃圾範圍,而且這就像是越界存取一樣是非法存取。
為了避免這個問題可以使用 dynamic_cast<typename*>(T) 編譯器將自動檢視是否為合法行為。

安全的向下轉型

剛剛我們的 b 地址所初始化的為子類別型態初始化,只不過我們宣告成父類別的檢視行為。
我們可以將他安全的向下轉型,因為他當初初始的話就是一個 B 的型態,轉型後不會造成越界存取的非法行為。
// 向下轉型
cout << "(*b).i=" << dynamic_cast<B &>(*b).i << endl;
如果你很確定保證絕對不會出錯那麼也可以使用 static_cast<>
// 向下轉型
cout << "(*b).i=" << static_cast<B &>(*b).i << endl;
在向上轉型時,因為保證不會出意外所以使用兩者不會有什麼區別,只不過使用 dynamic_cast<> 可以具備上下轉型的閱讀語意,可能比較容易閱讀代碼。

非法的向下轉型

// 非法的向下轉型
cout << "(A->B). = " << static_cast<B &>(*a).i << endl;
這將會導致越界的非法存取,程式會崩潰沒有回應

範例代碼

/*****************************************************************
Name : 
Date : 2017/03/22
By   : CharlotteHonG
Final: 2017/03/22
*****************************************************************/
#include <iostream>
using namespace std;

class A {
public:
    A(){}
    void virtual fun(){
        cout << "this is A" << endl;
    }
};

class B: public A{
public:
    B(){}
    void fun(){
        cout << "this is B" << endl;
    }
    int i=1;
};

/*==============================================================*/
int main(int argc, char const *argv[]){
    // 用父類別來操作所有指標
    A *a, *b;
    a = new A;
    b = new B;

    // 自動選擇正確的類型
    (*a).fun();
    (*b).fun();

    // 無法存取 (找不到該成員)
    // cout << "(*b).i=" << (*b).i << endl; // Error

    // 安全向下轉型
    cout << "(*b).i = " << dynamic_cast<B &>(*b).i << endl;

    // 非法的向下轉型
    cout << "(A->B). = " << static_cast<B &>(*a).i << endl;

    return 0;
}
/*==============================================================*/

沒有留言:

張貼留言