Polymorphism and Virtual Functions Cheng-Chin Chiang
Virtual Function Basics 多 型 (Polymorphism) 賦 予 一 個 函 數 多 種 意 涵, 存 在 於 同 一 類 別 之 內 祖 先 類 別 與 後 代 類 別 間 物 件 導 向 程 式 設 計 基 本 原 理 虛 擬 函 數 (Virtual Function) 一 種 可 以 動 態 連 結 (Dynamic Binding) 或 遲 連 結 (Late Binding) 的 函 數 不 是 由 編 譯 器 來 決 定 多 型 的 函 數 中 哪 一 個 版 本 的 函 數 被 呼 叫 執 行 時 才 決 定 被 呼 叫 的 版 本 決 定 原 則 : 根 據 執 行 呼 叫 的 類 別 實 體 來 呼 叫 該 實 體 所 屬 類 別 的 函 數 版 本
Example with no Virtual Functions class C public: string tostring() return "class C"; ; class B: public C string tostring() return "class B"; ; class A: public B string tostring() return "class A"; ; void displayobject(c x) cout << x.tostring().data() << endl; int main() displayobject(a()); displayobject(b()); displayobject(c()); return 0; 結 果 : class C class C class C
Example with Virtual Functions class C public: virtual string tostring() return "class C"; ; class B: public C string tostring() return "class B"; ; class A: public B string tostring() return "class A"; ; void displayobject(c *p) cout << p->tostring().data() << endl; int main() A a = A(); B b = B(); C c = C(); displayobject(&a); displayobject(&b); displayobject(&c); return 0; 結 果 : class A class B class C
Why using virtual functions? 原 因 如 前 例, 當 我 們 在 設 計 class C 時, 可 能 還 不 知 道 後 面 的 設 計 師 會 根 據 class C 訂 出 那 些 衍 生 類 別 這 些 衍 生 類 別 中 可 能 會 定 義 各 自 不 同 的 tostring() 函 數, 而 class C 中 並 無 法 得 知 有 何 不 同 為 能 透 過 基 礎 類 別 物 件 來 執 行 基 礎 類 別 和 衍 生 類 別 間 的 多 型 呼 叫 ( 允 許 執 行 時 動 態 決 定 呼 叫 適 當 的 版 本 ), 可 以 在 基 礎 類 別 (class C) 中 將 tostring() 宣 告 為 虛 擬 函 數 注 意 須 透 過 物 件 指 標 或 物 件 參 考 來 呼 叫 避 免 割 除 問 題 (Slicing Problem) 基 礎 類 別 宣 告 為 virtual 的 函 數, 在 其 所 有 衍 生 類 別 中 也 自 動 成 為 virtual 函 數, 不 需 要 再 特 別 宣 告 其 為 virtual
Overriding vs. Redefining in Derived Classes 在 衍 生 類 別 中 重 新 設 計 虛 擬 函 數 稱 為 overriding 重 新 設 計 非 虛 擬 函 數 稱 為 redefining
Pros and Cons of Virtual Functions 優 點 (Pros) 多 型 呼 叫 的 彈 性 缺 點 (Cons) 執 行 時 效 率 較 低 使 用 較 多 記 憶 體 動 態 連 結 需 要 而 外 執 行 動 作, 較 耗 時 間
Inner Workings of Virtual Functions 虛 擬 函 數 表 (Virtual function table) 由 編 譯 器 產 生 表 中 含 有 知 道 各 個 虛 擬 函 數 的 指 標 指 到 函 數 程 式 碼 所 在 的 位 置 根 據 類 別 所 產 生 的 實 體 物 件 中 也 須 包 含 指 到 虛 擬 函 數 表 的 指 標
Pure Virtual Functions 類 別 繼 承 原 則 提 醒 基 礎 類 別 的 成 員 較 具 籠 統 性 與 共 通 性 ( 所 以 基 礎 類 別 較 為 抽 象 ) 基 礎 類 別 中 的 函 數 被 設 計 來 執 行 較 共 通 的 動 作 在 基 礎 類 別 中 的 虛 擬 函 數 可 能 因 為 衍 生 類 別 太 過 多 元, 導 致 在 此 虛 擬 函 數 中 完 全 沒 有 共 通 性 的 動 作 需 要 執 行 此 虛 擬 函 數 變 成 是 一 個 有 名 無 實 的 純 虛 擬 函 數 (Pure Virtual Function) 此 此 純 虛 擬 函 數 的 存 在 目 的 僅 為 了 讓 衍 生 類 別 繼 承 一 個 共 通 名 稱 的 函 數, 然 後 再 於 各 衍 生 類 別 中 予 以 按 照 各 自 需 求 來 redefine 宣 告 純 虛 擬 函 數 的 語 法 virtual 回 傳 資 料 型 態 函 數 名 稱 ( 參 數 列 )= 0;
Example of Pure Virtual Function class Triangle: public Figure.. void draw();.. ; class Figure.. virtual void draw() = 0;.. ; class Circle : public Figure.. void draw();.. ; class Rectangle : public Figure.. void draw();.. ;
Abstract Base Classes 抽 象 類 別 : 擁 有 一 個 或 多 個 純 虛 擬 函 數 的 類 別 稱 為 抽 象 類 別 只 能 當 基 礎 類 別 使 用 不 可 利 用 抽 象 類 別 產 生 實 體 物 件 ( 因 為 有 成 員 尚 未 有 完 整 的 定 義 ) 一 個 衍 生 類 別 繼 承 自 抽 象 類 別 時 如 果 未 能 完 成 所 有 純 虛 擬 函 數 的 定 義, 則 此 衍 生 類 別 也 仍 然 是 一 個 抽 象 類 別 俄
例 題 : 抽 象 幾 何 類 別 class GeometricObject protected: GeometricObject(); GeometricObject(string color, bool filled); public: string getcolor(); void setcolor(string color); bool isfilled(); void setfilled(bool filled); string tostring(); virtual double getarea() = 0; virtual double getperimeter() = 0; private: string color; bool filled; ; class Rectangle: public GeometricObject public: Rectangle(); Rectangle(double width, double height); Rectangle(double width, double height, string color, bool filled); double getwidth(); void setwidth(double); double getheight(); void setheight(double); double getarea(); double getperimeter(); private: double width; double height;
例 題 : 抽 象 幾 何 類 別 class Circle: public GeometricObject public: Circle(); Circle(double); Circle(double radius, string color, bool filled); double getradius(); void setradius(double); double getarea(); double getperimeter(); double getdiameter(); private: double radius; ;
Assignment between Base and Derived Classes 基 礎 類 別 與 衍 生 類 別 間 的 指 定 (assignment) 基 礎 類 別 物 件 = 衍 生 類 別 物 件 OK 衍 生 類 別 物 件 = 基 礎 類 別 物 件 Not OK class Pet public: ; string name; virtual void print() const; Dog vdog; Pet vpet; vdog.name = Tiny ; vdog.breed = Great Dane ; vpet = vdog; class Dog : public Pet public: string breed; virtual void print() const; ; OK, 但 會 發 生 割 除 問 題 (Slicing Problem)
Slicing Problem 將 衍 生 類 別 指 定 給 基 礎 類 別 後, 導 致 部 份 成 員 遺 失 ( 無 效 ) 例 如 前 例 :vpet = vdog 指 定 之 後,vdog 中 的 breed 欄 位 變 為 無 效 所 以 cout << vpet.breed; 錯 誤!!!
Slicing Problem Fix 利 用 指 標 可 以 避 免 割 除 問 題 但 是 Pet *ppet; Dog *pdog; pdog = new Dog; pdog->name = "Tiny"; pdog->breed = "Great Dane"; ppet = pdog; cout << ppet->breed; 仍 然 錯 誤! 因 為 Pet 類 別 並 未 定 義 breed 欄 位! 解 決 方 法 : 利 用 虛 擬 函 數 來 輸 出 breed 欄 位, 因 為 虛 擬 函 數 有 根 據 實 體 物 件 的 所 屬 類 別 來 動 態 呼 叫 多 型 功 能
class Pet public: string name; virtual void print() const; ; class Dog : public Pet public: string breed; virtual void print() const; ; void Pet::print() const cout << this->name << endl; void Dog::print() const cout << this->name << endl; cout << this->breed << endl; Pet *ppet; Dog *pdog; pdog = new Dog; pdog->name = "Tiny"; pdog->breed = "Great Dane"; ppet = pdog; ppet->print(); // 呼 叫 Dog 的 print(), 因 為 ppet 指 到 一 個 dog 實 體 物 件
Virtual Destructors Base *pbase = new Derived; delete pbase; // 問 題 : 呼 叫 Base 的 解 構 子 還 是 Derived 的 解 構 子? ANS:Base 的 解 構 子 修 正 方 法 : 將 Base 的 解 構 子 宣 告 為 virtual
Casting 可 否 使 用 下 面 的 靜 態 冠 型? Pet vpet; Dog vdog; vdog = static_cast<dog>(vpet); ILLEGAL! Downcasting vpet = static_cast<pet>(vdog); Upcasting 是 被 允 許 的 把 後 代 類 別 物 件 轉 為 祖 先 類 別 物 件 LEGAL! Upcasting
Downcasting 把 祖 先 類 別 物 件 轉 型 為 後 代 類 別 物 件 後 代 類 別 多 出 來 的 資 料 成 員 須 備 妥 Downcasting 是 有 風 險 的! 可 以 利 用 動 態 冠 型 來 強 迫 執 行 Pet *ppet; ppet = new Dog; Dog *pdog = dynamic_cast<dog*>(ppet); Downcasting 很 少 被 使 用, 因 為 必 須 備 妥 所 有 在 後 代 類 別 中 新 增 屬 性 成 員 所 有 成 員 函 數 必 須 宣 告 為 虛 擬 函 數
例 題 : A function for displaying a geometric object void displaygeometricobject(geometricobject &object) cout << "The area is " << object.getarea() << endl; cout << "The perimeter is " << object.getperimeter() << endl; GeometricObject *p = &object; Circle *p1 = dynamic_cast<circle*>(p); Rectangle *p2 = dynamic_cast<rectangle*>(p); if (p1!= 0) cout << "The radius is " << p1->getradius() << endl; cout << "The diameter is " << p1->getdiameter() << endl; if (p2!= 0) cout << "The width is " << p2->getwidth() << endl; cout << "The height is " << p2->getheight() << endl; int main() Circle circle(5); Rectangle rectangle(5, 3); cout << "Circle info: " << endl; displaygeometricobject(circle); cout << "\nrectangle info: " << endl; displaygeometricobject(rectangle); return 0; Circle info: The area is 78.5397 The perimeter is 31.4159 The radius is 5 The diameter is 10 Rectangle info: The area is 15 The perimeter is 16 The width is 5 The height is 3
typeid operator 取 得 物 件 類 別 的 相 關 資 訊 傳 回 一 個 type_info 類 別 的 物 件 (#include typeinfo.h ) class abc int a; int b; ; int main() abc x; cout << typeid(x).name() << endl; 結 果 : class abc type_info info; info = typeid(x); cout << info.name(); 錯 誤! type_info 的 建 構 子 和 指 定 運 算 子 都 是 private