明輝手游網(wǎng)中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

C++多態(tài)技術

[摘要]C++多態(tài)技術2004-01-08 榮耀 點擊: 18C++多態(tài)技術摘要本文描述了C++中的各種多態(tài)性。重點闡述了面向?qū)ο蟮膭討B(tài)多態(tài)和基于模板的靜態(tài)多態(tài),并初步探討了兩種技術的結合使用。 關鍵詞多態(tài) 繼承 虛函數(shù) 模板 宏 函數(shù)重載 泛型編程 泛型模式 導言多態(tài)(...

C++多態(tài)技術2004-01-08    榮耀       點擊: 18C++多態(tài)技術摘要

本文描述了C++中的各種多態(tài)性。重點闡述了面向?qū)ο蟮膭討B(tài)多態(tài)和基于模板的靜態(tài)多態(tài),并初步探討了兩種技術的結合使用。 

關鍵詞

多態(tài)  繼承  虛函數(shù)  模板  宏  函數(shù)重載 泛型編程  泛型模式  

導言

多態(tài)(polymorphism)一詞最初來源于希臘語polumorphos,含義是具有多種形式或形態(tài)的情形。在程序設計領域,一個廣泛認可的定義是“一種將不同的特殊行為和單個泛化記號相關聯(lián)的能力”。和純粹的面向?qū)ο蟪绦蛟O計語言不同,C++中的多態(tài)有著更廣泛的含義。除了常見的通過類繼承和虛函數(shù)機制生效于運行期的動態(tài)多態(tài)(dynamic polymorphism)外,模板也允許將不同的特殊行為和單個泛化記號相關聯(lián),由于這種關聯(lián)處理于編譯期而非運行期,因此被稱為靜態(tài)多態(tài)(static polymorphism)。 
事實上,帶變量的宏和函數(shù)重載機制也允許將不同的特殊行為和單個泛化記號相關聯(lián)。然而,習慣上我們并不將它們展現(xiàn)出來的行為稱為多態(tài)(或靜態(tài)多態(tài))。今天,當我們談及多態(tài)時,如果沒有明確所指,默認就是動態(tài)多態(tài),而靜態(tài)多態(tài)則是指基于模板的多態(tài)。不過,在這篇以C++各種多態(tài)技術為主題的文章中,我們首先還是回顧一下C++社群爭論已久的另一種“多態(tài)”:函數(shù)多態(tài)(function polymorphism),以及更不常提的“宏多態(tài)(macro polymorphism)”。 

函數(shù)多態(tài)

也就是我們常說的函數(shù)重載(function overloading)。基于不同的參數(shù)列表,同一個函數(shù)名字可以指向不同的函數(shù)定義: 

// overload_poly.cpp

#include <iostream>
#include <string>

// 定義兩個重載函數(shù)

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);                // 兩個整數(shù)相加
    int s = my_add(1, "2");              // 一個整數(shù)和一個字符串相加
    std::cout << "i = " << i << "\n";
    std::cout << "s = " << s << "\n";


根據(jù)參數(shù)列表的不同(類型、個數(shù)或兼而有之),my_add(1, 2)和my_add(1, "2")被分別編譯為對my_add(int, int)和my_add(int, std::string)的調(diào)用。實現(xiàn)原理在于編譯器根據(jù)不同的參數(shù)列表對同名函數(shù)進行名字重整,而后這些同名函數(shù)就變成了彼此不同的函數(shù)。比方說,也許某個編譯器會將my_add()函數(shù)名字分別重整為my_add_int_int()和my_add_int_str()。 

宏多態(tài)

帶變量的宏可以實現(xiàn)一種初級形式的靜態(tài)多態(tài): 
// 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);                        // 兩個整數(shù)相加
    std::string s = ADD(s1, s2);                // 兩個字符串“相加”
    std::cout << "i = " << i << "\n";
    std::cout << "s = " << s << "\n";
}
當程序被編譯時,表達式ADD(i1, i2)和ADD(s1, s2)分別被替換為兩個整數(shù)相加和兩個字符串相加的具體表達式。整數(shù)相加體現(xiàn)為求和,而字符串相加則體現(xiàn)為連接。程序的輸出結果符合直覺: 
1 + 2 = 3
Hello, + world! = Hello, world!

動態(tài)多態(tài)

這就是眾所周知的的多態(tài),F(xiàn)代面向?qū)ο笳Z言對這個概念的定義是一致的。其技術基礎在于繼承機制和虛函數(shù)。例如,我們可以定義一個抽象基類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的指針(或引用)來操縱具體對象。通過指向基類對象的指針(或引用)來調(diào)用一個虛函數(shù),會導致對被指向的具體對象之相應成員的調(diào)用:

// dynamic_poly_1.cpp

#include <iostream>
#include <vector>
#include "dynamic_poly.h"

// 通過指針run任何vehicle
void run_vehicle(const Vehicle* vehicle)
{
    vehicle->run();            // 根據(jù)vehicle的具體類型調(diào)用對應的run()
}

int main()
{
    Car car;
    Airplane airplane;
    run_vehicle(&car);         // 調(diào)用Car::run()
    run_vehicle(&airplane);    // 調(diào)用Airplane::run()
}

此例中,關鍵的多態(tài)接口元素為虛函數(shù)run()。由于run_vehicle()的參數(shù)為指向基類Vehicle的指針,因而無法在編譯期決定使用哪一個版本的run()。在運行期,為了分派函數(shù)調(diào)用,虛函數(shù)被調(diào)用的那個對象的完整動態(tài)類型將被訪問。這樣一來,對一個Car對象調(diào)用run_vehicle(),實際上將調(diào)用Car::run(),而對于Airplane對象而言將調(diào)用Airplane::run()。
或許動態(tài)多態(tài)最吸引人之處在于處理異質(zhì)對象集合的能力: 

// dynamic_poly_2.cpp

#include <iostream>
#include <vector>
#include "dynamic_poly.h"

// run異質(zhì)vehicles集合
void run_vehicles(const std::vector<Vehicle*>& vehicles)
{
    for (unsigned int i = 0; i < vehicles.size(); ++i)
    {
        vehicles[i]->run();     // 根據(jù)具體vehicle的類型調(diào)用對應的run()
    }
}

int main()
{
    Car car;
    Airplane airplane;
    std::vector<Vehicle*> v;    // 異質(zhì)vehicles集合
    v.push_back(&car);
    v.push_back(&airplane);
    run_vehicles(v);            // run不同類型的vehicles
}
在run_vehicles()中,vehicles[i]->run()依據(jù)正被迭代的元素的類型而調(diào)用不同的成員函數(shù)。這從一個側面體現(xiàn)了面向?qū)ο缶幊田L格的優(yōu)雅。

靜態(tài)多態(tài)

如果說動態(tài)多態(tài)是通過虛函數(shù)來表達共同接口的話,那么靜態(tài)多態(tài)則是通過“彼此單獨定義但支持共同操作的具體類”來表達共同性,換句話說,必須存在必需的同名成員函數(shù)。 
我們可以采用靜態(tài)多態(tài)機制重寫上一節(jié)的例子。這一次,我們不再定義vehicles類層次結構,相反,我們編寫彼此無關的具體類Car和Airplane(它們都有一個run()成員函數(shù)): 

// 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();            // 根據(jù)vehicle的具體類型調(diào)用對應的run()
}
 
int main()
{
    Car car;
    Airplane airplane;
    run_vehicle(car);         // 調(diào)用Car::run()
    run_vehicle(airplane);    // 調(diào)用Airplane::run()

現(xiàn)在Vehicle用作模板參數(shù)而非公共基類對象(事實上,這里的Vehicle只是一個符合直覺的記號而已,此外別無它意)。經(jīng)過編譯器處理后,我們最終會得到run_vehicle<Car>()和 run_vehicle<Airplane>()兩個不同的函數(shù)。這和動態(tài)多態(tài)不同,動態(tài)多態(tài)憑借虛函數(shù)分派機制在運行期只有一個run_vehicle()函數(shù)。 
我們無法再透明地處理異質(zhì)對象集合了,因為所有類型都必須在編譯期予以決定。不過,為不同的vehicles引入不同的集合只是舉手之勞。由于無需再將集合元素局限于指針或引用,我們現(xiàn)在可以從執(zhí)行性能和類型安全兩方面獲得好處: 

// static_poly_2.cpp

#include <iostream>
#include <vector>
#include "static_poly.h"

// run同質(zhì)vehicles集合
template <typename Vehicle>
void run_vehicles(const std::vector<Vehicle>& vehicles)
{
    for (unsigned int i = 0; i < vehicles.size(); ++i) 
    {
        vehicles[i].run();            // 根據(jù)vehicle的具體類型調(diào)用相應的run()
    }
}

int main()
{
    Car car1, car2;
    Airplane airplane1, airplane2;

    std::vector<Car> vc;              // 同質(zhì)cars集合
    vc.push_back(car1);
    vc.push_back(car2);
    //vc.push_back(airplane1);        // 錯誤:類型不匹配
    run_vehicles(vc);                 // run cars

    std::vector<Airplane> vs;         // 同質(zhì)airplanes集合
    vs.push_back(airplane1);
    vs.push_back(airplane2);
    //vs.push_back(car1);             // 錯誤:類型不匹配
    run_vehicles(vs);                 // run airplanes
}

兩種多態(tài)機制的結合使用 

在一些高級C++應用中,我們可能需要結合使用動態(tài)多態(tài)和靜態(tài)多態(tài)兩種機制,以期達到對象操作的優(yōu)雅、安全和高效。例如,我們既希望一致而優(yōu)雅地處理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異質(zhì)vehicles集合
void run_vehicles(const std::vector<Vehicle*>& vehicles)
{
    for (unsigned int i = 0; i < vehicles.size(); ++i)
    {
        vehicles[i]->run();                 // 根據(jù)具體的vehicle類型調(diào)用對應的run()
    }
}

// 為某種特定的aircrafts同質(zhì)對象集合進行“空中加油”
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;                // 異質(zhì)vehicles集合
    v.push_back(&car1);
    v.push_back(&airplane1);
    v.push_back(&airship1);
    run_vehicles(v);                        // run不同種類的vehicles

    std::vector<Airplane> vp;               // 同質(zhì)airplanes集合
    vp.push_back(airplane1);
    vp.push_back(airplane2);
    add_oil_to_aircrafts_in_the_sky(vp);    // 為airplanes進行“空中加油”

    std::vector<Airship> vs;                // 同質(zhì)airships集合
    vs.push_back(airship1);
    vs.push_back(airship2);
    add_oil_to_aircrafts_in_the_sky(vs);    // 為airships進行“空中加油”


我們保留了類層次結構,目的是為了能夠利用run_vehicles()一致而優(yōu)雅地處理異質(zhì)對象集合vehicles的run問題。同時,利用函數(shù)模板add_oil_to_aircrafts_in_the_sky<Aircraft>(),我們?nèi)匀豢梢蕴幚硖囟ǚN類的vehicles — aircrafts(包括airplanes和airships)的“空中加油”問題。其中,我們避開使用指針,從而在執(zhí)行性能和類型安全兩方面達到了預期目標。
 
結語 

長期以來,C++社群對于多態(tài)的內(nèi)涵和外延一直爭論不休。在comp.object這樣的網(wǎng)絡論壇上,此類話題爭論至今仍隨處可見。曾經(jīng)有人將動態(tài)多態(tài)(dynamic polymorphism)稱為inclusion polymorphism,而將靜態(tài)多態(tài)(static polymorphism)稱為parametric polymorphism或parameterized polymorphism。 

我注意到2003年斯坦福大學公開的一份C++ and Object-Oriented Programming教案中明確提到了函數(shù)多態(tài)概念:Function overloading is also referred to as function polymorphism as it involves one function having many forms。文后的“參考文獻”單元給出了這個網(wǎng)頁鏈接。
 
可能你是第一次看到宏多態(tài)(macro polymorphism)這個術語。不必訝異 — 也許我就是造出這個術語的“第一人”。顯然,帶變量的宏(或類似于函數(shù)的宏或偽函數(shù)宏)的替換機制除了免除小型函數(shù)的調(diào)用開銷之外,也表現(xiàn)出了類似的多態(tài)性。在我們上面的例子中,字符串相加所表現(xiàn)出來的符合直覺的連接操作,事實上是由底部運算符重載機制(operator overloading)支持的。值得指出的是,C++社群中有人將運算符重載所表現(xiàn)出來的多態(tài)稱為ad hoc polymorphism。 

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

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

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

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

參考文獻
1. David Vandevoorde, Nicolai M. Josuttis, C++ Templates: The Complete Guide, Addison Wesley, 2002.
2. Chris Neumann, CS193d (Summer 2003) C++ and Object-Oriented Programming, http://www.stanford.edu/class/cs193d/, 2003.