C++程序設計中的多態技術研究

導言

多態(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()。

C++程序設計中的多態技術研究
更多內容請看C/C++進階技術文檔 C/C++相關文章專題,或

宏多態

帶變量的宏可以實現一種初級形式的靜態多態:

// macro_poly.cpp

#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()依據正被叠代的元素的類型而調用不同的成員函數。這從一個側面體現了面向對象編程風格的優雅。

C++程序設計中的多態技術研究
更多內容請看C/C++進階技術文檔 C/C++相關文章專題,或

靜態多態

假如說動態多態是通過虛函數來表達共同接口的話,那麽靜態多態則是通過「彼此單獨定義但支持共同操作的具體類」來表達共同性,換句話說,必須存在必需的同名成員函數。

我們可以采用靜態多態機制重寫上一節的例子。這一次,我們不再定義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

}

C++程序設計中的多態技術研究
更多內容請看C/C++進階技術文檔 C/C++相關文章專題,或

兩種多態機制的結合使用

在一些高級C++應用中,我們可能需要結合使用動態多態和靜態多態兩種機制,以期達到對象操作的優雅、安全和高效。例如,我們既希望一致而優雅地處理vehicles的run問題,又希望「安全而高效」地完成給飛行器(飛機、飛艇等)進行「空中加油」這樣的高難度動作。爲此,我們首先將上面的vehicles類層次結構改寫如下:

// dscombine_poly.h

#include <iostream>

#include <vector>

// 公共抽象基類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";

}

void add_oil() const

{

std::cout << "add oil to airplane\n";

}

};

// 派生于Vehicle的具體類Airship

class Airship: public Vehicle

{

public:

virtual void run() const

{

std::cout << "run a airship\n";

}

void add_oil() const

{

std::cout << "add oil to airship\n";

}

};

我們理想中的應用程序可以編寫如下:

// dscombine_poly.cpp

#include <iostream>

#include <vector>

#include "dscombine_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()

}

}

// 爲某種特定的aircrafts同質對象集合進行「空中加油」

template <typename Aircraft>

void add_oil_to_aircrafts_in_the_sky(const std::vector<Aircraft>& aircrafts)

{

for (unsigned int i = 0; i < aircrafts.size(); ++i)

{

aircrafts[i].add_oil();

}

}

int main()

{

Car car1, car2;

Airplane airplane1, airplane2;

Airship airship1, airship2;

std::vector<Vehicle*> v; // 異質vehicles集合

v.push_back(&car1);

v.push_back(&airplane1);

v.push_back(&airship1);

run_vehicles(v); // run不同種類的vehicles

std::vector<Airplane> vp; // 同質airplanes集合

vp.push_back(airplane1);

vp.push_back(airplane2);

add_oil_to_aircrafts_in_the_sky(vp); // 爲airplanes進行「空中加油」

std::vector<Airship> vs; // 同質airships集合

vs.push_back(airship1);

vs.push_back(airship2);

add_oil_to_aircrafts_in_the_sky(vs); // 爲airships進行「空中加油」

}

我們保留了類層次結構,目的是爲了能夠利用run_vehicles()一致而優雅地處理異質對象集合vehicles的run問題。同時,利用函數模板add_oil_to_aircrafts_in_the_sky<Aircraft>(),我們仍然可以處理特定種類的vehicles — aircrafts(包括airplanes和airships)的「空中加油」問題。其中,我們避開使用指針,從而在執行性能和類型安全兩方面達到了預期目標。

C++程序設計中的多態技術研究
更多內容請看C/C++進階技術文檔 C/C++相關文章專題,或 結束語

長期以來,C++社群對于多態的內涵和外延一直爭論不休。在comp.object這樣的網絡論壇上,此類話題爭論至今仍隨處可見。

曾經有人將動態多態(dynamic polymorphism)稱爲inclusion polymorphism,而將靜態多態(static polymorphism)稱爲parametric polymorphism或parameterized polymorphism。

我注重到2003年斯坦福大學公開的一份C++ and Object-Oriented PRogramming教案中明確提到了函數多態概念:Function overloading is also referred to as function polymorphism as it involves one function having many forms。文後的「參考文獻」單元給出了這個網頁鏈接。

可能你是第一次看到宏多態(macro polymorphism)這個術語。不必訝異 — 也許我就是造出這個術語的「第一人」。顯然,帶變量的宏(或類似于函數的宏或僞函數宏)的替換機制除了免除小型函數的調用開銷之外,也表現出了類似的多態性。在我們上面的例子中,字符串相加所表現出來的符合直覺的連接操作,事實上是由底部運算符重載機制(Operator overloading)支持的。值得指出的是,C++社群中有人將運算符重載所表現出來的多態稱爲ad hoc polymorphism。

David Vandevoorde和Nicolai M. Josuttis在他們的著作C++ Templates: The Complete Guide一書中系統地闡述了靜態多態和動態多態技術。因爲認爲「和其他語言機制關系不大」,這本書沒有提及「宏多態」(以及「函數多態」)。(需要說明的是,筆者本人是這本書的繁體中文版譯者之一,本文正是基于這本書的第14章The Polymorphic Power of Templates編寫而成)

動態多態只需要一個多態函數,生成的可執行代碼尺寸較小,靜態多態必須針對不同的類型産生不同的模板實體,尺寸會大一些,但生成的代碼會更快,因爲無需通過指針進行間接操作。靜態多態比動態多態更加類型安全,因爲全部綁定都被檢查于編譯期。正如前面例子所示,你不可將一個錯誤的類型的對象插入到從一個模板實例化而來的容器之中。此外,正如你已經看到的那樣,動態多態可以優雅地處理異質對象集合,而靜態多態可以用來實現安全、高效的同質對象集合操作。

靜態多態爲C++帶來了泛型編程(generic programming)的概念。泛型編程可以認爲是「組件功能基于框架整體而設計」的模板編程。STL就是泛型編程的一個典範。STL是一個框架,它提供了大量的算法、容器和叠代器,全部以模板技術實現。從理論上講,STL的功能當然可以使用動態多態來實現,不過這樣一來其性能必將大打折扣。

靜態多態還爲C++社群帶來了泛型模式(generic patterns)的概念。理論上,每一個需要通過虛函數和類繼續而支持的設計模式都可以利用基于模板的靜態多態技術(甚至可以結合使用動態多態和靜態多態兩種技術)而實現。正如你看到的那樣,Andrei Alexandrescu的天才作品Modern C++ Design: Generic Programming and Design Patterns Applied(Addison-Wesley)和Loki程序庫已經走在了我們的前面。

C++程序設計中的多態技術研究
更多內容請看C/C++進階技術文檔 C/C++相關文章專題,或

 
面向對象程序設計語言C++中的多態性
C++是以C語言爲基礎,支持數據抽象和面向對象的程序設計語言。C++對C語言的擴充部分汲取了許多著名語言中最優秀的特征,如從Algo168中吸取了操作符重載機制等。由于C++語言具有與C語言一樣的高執行效率,並容易被熟悉...查看完整版>>面向對象程序設計語言C++中的多態性
 
理解C++面向對象程序設計中的抽象理論
  很多書在一開始就開始學習josephus問題,爲了讓大家前面學起來較爲輕易我把前面涉及到此問題的地方都故意去掉了,現在我們已經學習過了結構體和類,所以放在這裏學習可能更合適一些。 ...查看完整版>>理解C++面向對象程序設計中的抽象理論
 
Vb 6 中的多態
Vb 6 中的多態最近在寫一個MDI窗體的程序,每調用一個子窗體時都要對其做一些操作。于是我就用一個SUB 來做顯示子窗體的操作 Private Sub showFrm(ByRef tFrm As Form) {代碼。。。。。。} tFrm.s...查看完整版>>Vb 6 中的多態
 
PHP 5.0 中的對象重載技術研究
  一、簡介  很幸運,PHP 5.0中引入了對象重載技術。本文將探討對于方法__call(),__set()以及__get()進行重載的可能性。在對重載理論作簡單介紹後,我們將通過兩個例子直奔主題:第一例,實現持續存儲類;第二例...查看完整版>>PHP 5.0 中的對象重載技術研究
 
C++程序設計從零開始之賦值操作符
賦值語句   前面已經說明,要訪問內存,就需要相應的地址以表明訪問哪塊內存,而變量是一個映射,因此變量名就相當于一個地址。對于內存的操作,在一般情況下就只有讀取內存中的數值和將數值寫入內存(不考慮分配和...查看完整版>>C++程序設計從零開始之賦值操作符