多態polymorphism)一詞最初來源於希臘語polumorphos,含義是具有多種形式或形態的情形。在程序設計領域,一個廣泛認可的定義是“一種將不同的特殊行為和單個泛化記號相關聯的能力”。和純粹的面向對象程序設計語言不同,C++中的多態有著更廣泛的含義。除了常見的通過類繼承和虛函數機制生效於運行期的動態多態dynamic polymorphism)外,模板也允許將不同的特殊行為和單個泛化記號相關聯,由於這種關聯處理於編譯期而非運行期,因此被稱為靜態多態static polymorphism)。
事實上,帶變量的宏和函數重載機制也允許將不同的特殊行為和單個泛化記號相關聯。然而,習慣上我們並不將它們展現出來的行為稱為多態或靜態多態)。今天,當我們談及多態時,如果沒有明確所指,默認就是動態多態,而靜態多態則是指基於模板的多態。不過,在這篇以C++各種多態技術為主題的文章中,我們首先還是回顧一下C++社群爭論已久的另一種“多態”:函數多態function polymorphism),以及更不常提的“宏多態macro polymorphism)”。
函數多態
也就是我們常說的函數重載function overloading)。基於不同的參數列表,同一個函數名字可以指向不同的函數定義:// overload_poly.cpp
- #include <iostream>
- #include <string>
- // 定義兩個重載函數
- int my_add(int a, int b)
- {
- return a + b;
- }
- int my_add(int a, std::string b)
- {
- return a + atoi(b.c_str());
- }
- int main()
- {
- int i = my_add(1, 2); // 兩個整數相加
- int s = my_add(1, "2"); // 一個整數和一個字符串相加
- std::cout << "i = " << i << "\n";
- std::cout << "s = " << s << "\n";
- }
根據參數列表的不同類型、個數或兼而有之),my_add(1, 2)和my_add(1, "2")被分別編譯為對my_add(int, int)和my_add(int, std::string)的調用。實現原理在於編譯器根據不同的參數列表對同名函數進行名字重整,而後這些同名函數就變成了彼此不同的函數。比方說,也許某個編譯器會將my_add()函數名字分別重整為my_add_int_int()和my_add_int_str()。
宏多態
帶變量的宏可以實現一種初級形式的靜態多態:
- #include <iostream>
- #include <string>
- // 定義泛化記號:宏ADD
- #define ADD(A, B) (A) + (B);
- int main()
- {
- int i1(1), i2(2);
- std::string s1("Hello, "), s2("world!");
- int i = ADD(i1, i2); // 兩個整數相加
- std::string s = ADD(s1, s2); // 兩個字符串“相加”
- std::cout << "i = " << i << "\n";
- std::cout << "s = " << s << "\n";
- }
當程序被編譯時,表達式ADD(i1, i2)和ADD(s1, s2)分別被替換為兩個整數相加和兩個字符串相加的具體表達式。整數相加體現為求和,而字符串相加則體現為連接。
程序的輸出結果符合直覺:
- 1 + 2 = 3
- Hello, + world! = Hello, world!
動態多態
這就是眾所周知的的多態。現代面向對象語言對這個概念的定義是一致的。其技術基礎在於繼承機制和虛函數。例如,我們可以定義一個抽象基類Vehicle和兩個派生於Vehicle的具體類Car和Airplane:
- // dynamic_poly.h
- #include <iostream>
- // 公共抽象基類Vehicle
- class Vehicle
- {
- public:
- virtual void run() const = 0;
- };
- // 派生於Vehicle的具體類Car
- class Car: public Vehicle
- {
- public:
- virtual void run() const
- {
- std::cout << "run a car\n";
- }
- };
- // 派生於Vehicle的具體類Airplane
- class Airplane: public Vehicle
- {
- public:
- virtual void run() const
- {
- std::cout << "run a airplane\n";
- }
- };
客戶程序可以通過指向基類Vehicle的指針或引用)來操縱具體對象。通過指向基類對象的指針或引用)來調用一個虛函數,會導致對被指向的具體對象之相應成員的調用:
- // dynamic_poly_1.cpp
- #include <iostream>
- #include <vector>
- #include "dynamic_poly.h"
- // 通過指針run任何vehicle
- void run_vehicle(const Vehicle* vehicle)
- {
- vehicle->run(); // 根據vehicle的具體類型調用對應的run()
- }
- int main()
- {
- Car car;
- Airplane airplane;
- run_vehicle(&car); // 調用Car::run()
- run_vehicle(&airplane); // 調用Airplane::run()
- }
此例中,關鍵的多態接口元素為虛函數run()。由於run_vehicle()的參數為指向基類Vehicle的指針,因而無法在編譯期決定使用哪一個版本的run()。在運行期,為了分派函數調用,虛函數被調用的那個對象的完整動態類型將被訪問。這樣一來,對一個Car對象調用run_vehicle(),實際上將調用Car::run(),而對於Airplane對象而言將調用Airplane::run()。
或許動態多態最吸引人之處在於處理異質對象集合的能力:
- // dynamic_poly_2.cpp
- #include <iostream>
- #include <vector>
- #include "dynamic_poly.h"
- // run異質vehicles集合
- void run_vehicles(const std::vector<Vehicle*>& vehicles)
- {
- for (unsigned int i = 0; i < vehicles.size(); ++i)
- {
- vehicles[i]->run(); // 根據具體vehicle的類型調用對應的run()
- }
- }
- int main()
- {
- Car car;
- Airplane airplane;
- std::vector<Vehicle*> v; // 異質vehicles集合
- v.push_back(&car);
- v.push_back(&airplane);
- run_vehicles(v); // run不同類型的vehicles
- }
在run_vehicles()中,
- vehicles[i]->run()
依據正被迭代的元素的類型而調用不同的成員函數。這從一個側面體現了面向對象編程風格的優雅。
靜態多態
如果說動態多態是通過虛函數來表達共同接口的話,那麼靜態多態則是通過“彼此單獨定義但支持共同操作的具體類”來表達共同性,換句話說,必須存在必需的同名成員函數。
我們可以采用靜態多態機制重寫上一節的例子。這一次,我們不再定義vehicles類層次結構,相反,我們編寫彼此無關的具體類Car和Airplane它們都有一個run()成員函數):
- // static_poly.h
- #include <iostream>
- //具體類Car
- class Car
- {
- public:
- void run() const
- {
- std::cout << "run a car\n";
- }
- };
- //具體類Airplane
- class Airplane
- {
- public:
- void run() const
- {
- std::cout << "run a airplane\n";
- }
- };
run_vehicle()應用程序被改寫如下:
- // static_poly_1.cpp
- #include <iostream>
- #include <vector>
- #include "static_poly.h"
- // 通過引用而run任何vehicle
- template <typename Vehicle>
- void run_vehicle(const Vehicle& vehicle)
- {
- vehicle.run(); // 根據vehicle的具體類型調用對應的run()
- }
- int main()
- {
- Car car;
- Airplane airplane;
- run_vehicle(car); // 調用Car::run()
- run_vehicle(airplane); // 調用Airplane::run()
- }
現在Vehicle用作模板參數而非公共基類對象事實上,這裡的Vehicle只是一個符合直覺的記號而已,此外別無它意)。經過編譯器處理後,我們最終會得到run_vehicle<Car>()和 run_vehicle<Airplane>()兩個不同的函數。這和動態多態不同,動態多態憑借虛函數分派機制在運行期只有一個run_vehicle()函數。
我們無法再透明地處理異質對象集合了,因為所有類型都必須在編譯期予以決定。不過,為不同的vehicles引入不同的集合只是舉手之勞。由於無需再將集合元素局限於指針或引用,我們現在可以從執行性能和類型安全兩方面獲得好處:
- // static_poly_2.cpp
- #include <iostream>
- #include <vector>
- #include "static_poly.h"
- // run同質vehicles集合
- template <typename Vehicle>
- void run_vehicles(const std::vector<Vehicle>& vehicles)
- {
- for (unsigned int i = 0; i < vehicles.size(); ++i)
- {
- vehicles[i].run(); // 根據vehicle的具體類型調用相應的run()
- }
- }
- int main()
- {
- Car car1, car2;
- Airplane airplane1, airplane2;
- std::vector<Car> vc; // 同質cars集合
- vc.push_back(car1);
- vc.push_back(car2);
- //vc.push_back(airplane1); // 錯誤:類型不匹配
- run_vehicles(vc); // run cars
- std::vector<Airplane> vs; // 同質airplanes集合
- vs.push_back(airplane1);
- vs.push_back(airplane2);
- //vs.push_back(car1); // 錯誤:類型不匹配
- run_vehicles(vs); // run airplanes
- }