后記
結合前面的討論,我們可以看到,只要牽涉到了虛繼承,在訪(fǎng)問(wèn)父類(lèi)的成員變量時(shí)生成的代碼相當的低效,需要通過(guò)很多間接的計算來(lái)定位成員變量的地址。在指針類(lèi)型轉換,動(dòng)態(tài)轉型,及虛函數調用時(shí),也需要生成很多額外的代碼來(lái)調整this指針。象前一篇中對C170對象的obj.foo()和obj.f170()兩次調用,傳遞到兩個(gè)函數中的this指針居然是不一樣的。
前面我們碰到過(guò)的怪異行為還有很多,比如偏移值指針指向地址的前4字節,及C150、C170對象中的4字節0值的語(yǔ)義,為什么對C150和C170調用foo函數時(shí),this指針指向的不是子類(lèi)部分的起始位置而是祖父類(lèi)的起始位置,等等。去徹底的探究這些問(wèn)題的意義并不是很大。虛繼承的實(shí)現屬于編譯器廠(chǎng)商的行為,廠(chǎng)商出于不同的考慮,實(shí)現的方法也會(huì )大相徑庭。
對于傳統的C程序員,他們可能會(huì )認為C++的效率低。其實(shí)效率低是低在多態(tài)部分,因為這要在運行時(shí)通過(guò)虛表來(lái)決議出函數的地址。但對于設計而言,多態(tài)是一個(gè)非常強大的武器。多態(tài)也是面向對象設計的核心技術(shù)之一。雖然在執行的效率上有所損失,但對于大規模的程序設計,對于問(wèn)題域到模型的映射,使用以多態(tài)為核心的面向對象設計技術(shù)可以提高設計、實(shí)現及維護的效率,對于大部分的應用,總體來(lái)說(shuō)得大于失。
但是對于虛繼承,個(gè)人感覺(jué)只是為了解決菱形繼承及更復雜繼承問(wèn)題不得已而引入的一項機制,而且沒(méi)有完美的解決方案,不但大幅的損失效率,而且帶來(lái)了巨大的復雜性,使得繼承結構晦澀難懂。如非萬(wàn)不得已,且在自己清楚一切后果的情況下,建議不要使用。尤其是不要在被虛繼承的基類(lèi)中聲明非靜態(tài)的成員變量。
C++支持多種編程范型,面向過(guò)程式的、數據抽象及封裝、面向對象、現在又多了一種基于模板技術(shù)的泛型編程。我們以一個(gè)優(yōu)秀的開(kāi)源C++編程環(huán)境ACE(之所以叫編程環(huán)境,因為它提供了從類(lèi)庫到框架的多層次的支持)為例,看看在設計時(shí)的衡量及各種編程范型的運用契機。ACE分幾個(gè)層次,依次為:OS適配層、wrapper facade層、框架層、服務(wù)組件層。OS適配層、wrapper facade層主要運用了數據抽象及封裝,沒(méi)有用到多態(tài)及虛機制。因為這兩層的關(guān)鍵是效率。而且在這兩層中的類(lèi)的成員方法盡量的內聯(lián)。其實(shí)不使用多態(tài)及虛機制的話(huà),C++的效率和C應該是差不多的,但對象封裝導致了大量的瑣碎方法(如對成員變量的訪(fǎng)問(wèn)封裝,即set,get方法),而方法調用的成本也是相當高昂的(需要保存及恢復當前的執行環(huán)境上下文,參數的傳遞及返回值可能產(chǎn)生很多的臨時(shí)變量及對象等)。所以這兩層通過(guò)內聯(lián)來(lái)減少函數調用的開(kāi)銷(xiāo),提高執行效率。在框架層則使用了大量的設計模式,大量使用了多態(tài)機制及泛型技術(shù)。在這一層的主要關(guān)注點(diǎn)是結構的清晰,及實(shí)現設計上的語(yǔ)義。在這時(shí)多態(tài)機制在執行效率上的損失是可以忽略不計的。
最后,我用Lippman在他的經(jīng)典書(shū)籍《Inside the C++ Object Model》中關(guān)于描述虛成員函數章節中的一段話(huà)來(lái)做為這系列文章的結束。“Although I have a folder full of examples worked out and more than one algorithm for determining the proper offsets and adjustments, the material is simply too esoteric to warrant discussion in this text. My recommendation is not to declare nonstatic data members within a virtual base class. Doing that goes a long way in taming the complexity.”大意為在虛繼承時(shí)用以確定偏移地址及進(jìn)行this指針調整的可行算法很多,而且大都非常的詭異(這個(gè)我們已經(jīng)見(jiàn)識了:))。同時(shí)他建議不要在被虛繼承的基類(lèi)中聲明非靜態(tài)的成員變量,這樣做純屬自取煩惱。
另,感謝張水松和張建業(yè)這兩個(gè)土人,在寫(xiě)這些文章時(shí)和他們進(jìn)行了一些非常有益的探討。最后也是他們提醒我,不要再深入下去,以免走火入魔。
(全文完)
結合前面的討論,我們可以看到,只要牽涉到了虛繼承,在訪(fǎng)問(wèn)父類(lèi)的成員變量時(shí)生成的代碼相當的低效,需要通過(guò)很多間接的計算來(lái)定位成員變量的地址。在指針類(lèi)型轉換,動(dòng)態(tài)轉型,及虛函數調用時(shí),也需要生成很多額外的代碼來(lái)調整this指針。象前一篇中對C170對象的obj.foo()和obj.f170()兩次調用,傳遞到兩個(gè)函數中的this指針居然是不一樣的。
前面我們碰到過(guò)的怪異行為還有很多,比如偏移值指針指向地址的前4字節,及C150、C170對象中的4字節0值的語(yǔ)義,為什么對C150和C170調用foo函數時(shí),this指針指向的不是子類(lèi)部分的起始位置而是祖父類(lèi)的起始位置,等等。去徹底的探究這些問(wèn)題的意義并不是很大。虛繼承的實(shí)現屬于編譯器廠(chǎng)商的行為,廠(chǎng)商出于不同的考慮,實(shí)現的方法也會(huì )大相徑庭。
對于傳統的C程序員,他們可能會(huì )認為C++的效率低。其實(shí)效率低是低在多態(tài)部分,因為這要在運行時(shí)通過(guò)虛表來(lái)決議出函數的地址。但對于設計而言,多態(tài)是一個(gè)非常強大的武器。多態(tài)也是面向對象設計的核心技術(shù)之一。雖然在執行的效率上有所損失,但對于大規模的程序設計,對于問(wèn)題域到模型的映射,使用以多態(tài)為核心的面向對象設計技術(shù)可以提高設計、實(shí)現及維護的效率,對于大部分的應用,總體來(lái)說(shuō)得大于失。
但是對于虛繼承,個(gè)人感覺(jué)只是為了解決菱形繼承及更復雜繼承問(wèn)題不得已而引入的一項機制,而且沒(méi)有完美的解決方案,不但大幅的損失效率,而且帶來(lái)了巨大的復雜性,使得繼承結構晦澀難懂。如非萬(wàn)不得已,且在自己清楚一切后果的情況下,建議不要使用。尤其是不要在被虛繼承的基類(lèi)中聲明非靜態(tài)的成員變量。
C++支持多種編程范型,面向過(guò)程式的、數據抽象及封裝、面向對象、現在又多了一種基于模板技術(shù)的泛型編程。我們以一個(gè)優(yōu)秀的開(kāi)源C++編程環(huán)境ACE(之所以叫編程環(huán)境,因為它提供了從類(lèi)庫到框架的多層次的支持)為例,看看在設計時(shí)的衡量及各種編程范型的運用契機。ACE分幾個(gè)層次,依次為:OS適配層、wrapper facade層、框架層、服務(wù)組件層。OS適配層、wrapper facade層主要運用了數據抽象及封裝,沒(méi)有用到多態(tài)及虛機制。因為這兩層的關(guān)鍵是效率。而且在這兩層中的類(lèi)的成員方法盡量的內聯(lián)。其實(shí)不使用多態(tài)及虛機制的話(huà),C++的效率和C應該是差不多的,但對象封裝導致了大量的瑣碎方法(如對成員變量的訪(fǎng)問(wèn)封裝,即set,get方法),而方法調用的成本也是相當高昂的(需要保存及恢復當前的執行環(huán)境上下文,參數的傳遞及返回值可能產(chǎn)生很多的臨時(shí)變量及對象等)。所以這兩層通過(guò)內聯(lián)來(lái)減少函數調用的開(kāi)銷(xiāo),提高執行效率。在框架層則使用了大量的設計模式,大量使用了多態(tài)機制及泛型技術(shù)。在這一層的主要關(guān)注點(diǎn)是結構的清晰,及實(shí)現設計上的語(yǔ)義。在這時(shí)多態(tài)機制在執行效率上的損失是可以忽略不計的。
最后,我用Lippman在他的經(jīng)典書(shū)籍《Inside the C++ Object Model》中關(guān)于描述虛成員函數章節中的一段話(huà)來(lái)做為這系列文章的結束。“Although I have a folder full of examples worked out and more than one algorithm for determining the proper offsets and adjustments, the material is simply too esoteric to warrant discussion in this text. My recommendation is not to declare nonstatic data members within a virtual base class. Doing that goes a long way in taming the complexity.”大意為在虛繼承時(shí)用以確定偏移地址及進(jìn)行this指針調整的可行算法很多,而且大都非常的詭異(這個(gè)我們已經(jīng)見(jiàn)識了:))。同時(shí)他建議不要在被虛繼承的基類(lèi)中聲明非靜態(tài)的成員變量,這樣做純屬自取煩惱。
另,感謝張水松和張建業(yè)這兩個(gè)土人,在寫(xiě)這些文章時(shí)和他們進(jìn)行了一些非常有益的探討。最后也是他們提醒我,不要再深入下去,以免走火入魔。
(全文完)




