雷火电竞-中国电竞赛事及体育赛事平台

歡迎來(lái)到入門(mén)教程網(wǎng)!

C語(yǔ)言

當(dāng)前位置:主頁(yè) > 軟件編程 > C語(yǔ)言 >

關(guān)于C++面向?qū)ο笤O(shè)計(jì)的訪問(wèn)性問(wèn)題詳解

來(lái)源:本站原創(chuàng)|時(shí)間:2020-01-10|欄目:C語(yǔ)言|點(diǎn)擊:

前言

最近在看Scott Meyers大神的《Effective C++》和《More Effective C++》,雖然這兩本書(shū)都是古董級(jí)的教參了(當(dāng)然針對(duì)C++11/C++14作者所更新的《Modern Effective C++》英文已經(jīng)發(fā)售了,不過(guò)還沒(méi)中文翻譯版本),但是現(xiàn)在看來(lái)仍然收益匪淺,而且隨著對(duì)這個(gè)復(fù)雜語(yǔ)言了解的深入和實(shí)踐項(xiàng)目經(jīng)驗(yàn)的增加,很多東西和作者產(chǎn)生了一種共鳴,以前種種疑惑突然有種撥云霧而見(jiàn)天日、豁然開(kāi)朗的感覺(jué),也難怪被列為合格C++程序員之必讀書(shū)目。其實(shí)C++確實(shí)是個(gè)可怕的語(yǔ)言,于是市面上針對(duì)這個(gè)語(yǔ)言的教參也是聆郎滿(mǎn)目層出不窮,當(dāng)然水平也是參差不齊,像上面所說(shuō)的Meyers三部曲能夠歷久彌新,也凸顯了這些經(jīng)典教參的真正價(jià)值。

至于最近回歸C++本質(zhì),主要是覺(jué)得現(xiàn)在后臺(tái)開(kāi)發(fā)的RPC、MQ、分布式系統(tǒng)雖然被稱(chēng)的神乎其神的,但是作為成熟的組件絕大多數(shù)公司都可以是直接拿來(lái)主義,當(dāng)然也不可否認(rèn)其使用經(jīng)驗(yàn)的可貴,因?yàn)樽罱€(xiàn)上使用這些組件還是遇到或多或少不少問(wèn)題的,以后可以少走些坑,然而這種東西也是可遇難求的;反而C++語(yǔ)言本身的使用占用了程序員絕大多數(shù)的工作內(nèi)容,從而直接影響到項(xiàng)目的質(zhì)量和后續(xù)的可維護(hù)性。在此,侯捷老師的 勿在浮沙筑高臺(tái) 仍如警世名言響徹在耳,一個(gè)合格的程序員其扎實(shí)的基本功是多么重要。

C++面向?qū)ο蟮臇|西太多了:public、protected、private訪問(wèn)和繼承,virtual和多態(tài)、多繼承,外加const、缺省參數(shù)、名字查找等,光這些元素的排列組合就可以導(dǎo)出很多種情況,看似靈活多變,但不是每種情況都值得去嘗試的。

一、public繼承

public繼承意味著是”is-a”的關(guān)系,每個(gè)派生類(lèi)型對(duì)象也是一個(gè)基類(lèi)類(lèi)型對(duì)象,基類(lèi)支持的操作派生類(lèi)都支持,只不過(guò)派生類(lèi)比基類(lèi)更具體化一些而已,否則的話(huà)應(yīng)該將派生類(lèi)不支持的特性給踢出去,比如:

class Bird { ... };
class FlyingBird: public Bird {
public:
 virtual void fly(); ...
};
class Penguin: public Bird { ... };

所以,總體來(lái)說(shuō)public繼承是相對(duì)比較嚴(yán)格的契約關(guān)系。當(dāng)然public繼承是一個(gè)比較籠統(tǒng)的概念,細(xì)分下來(lái)還包括接口繼承、實(shí)現(xiàn)繼承、接口和實(shí)現(xiàn)繼承。

如果基類(lèi)聲明了一個(gè)pure virtual函數(shù),則其目的是讓派生類(lèi)只繼承該函數(shù)接口;如果基類(lèi)聲明了一個(gè)impure virtual函數(shù),就是讓派生類(lèi)繼承該函數(shù)的接口和其缺省實(shí)現(xiàn);如果某個(gè)成員函數(shù)是non-virtual函數(shù),則意味著它不打算在派生類(lèi)中有不同的行為,即派生類(lèi)繼承該函數(shù)接口及一份強(qiáng)制性實(shí)現(xiàn)。

對(duì)于pure virtual函數(shù)的接口聲明,基本沒(méi)有什么意義,而non-virtual成員也顯而易見(jiàn)。不過(guò)對(duì)于impure virtual虛函數(shù),看似提供了缺省實(shí)現(xiàn)使用起來(lái)會(huì)比較方便,而且派生類(lèi)可以覆蓋其實(shí)現(xiàn)也比較靈活,但是如果直接使用這種方式,那么如果基類(lèi)產(chǎn)生了新的派生類(lèi),但是恰好派生類(lèi)忘記對(duì)這個(gè)impure virtual函數(shù)進(jìn)行override,而其缺省實(shí)現(xiàn)又不滿(mǎn)足新派生類(lèi)的行為,那么新派生類(lèi)對(duì)象的調(diào)用將會(huì)引發(fā)問(wèn)題。所以如果想繼承接口,同時(shí)又提供缺省實(shí)現(xiàn),那么比較好的方式是將這兩個(gè)功能進(jìn)行分離,用一個(gè)pure virtual函數(shù)提供接口,再用一個(gè)non-virtual protected函數(shù)提供缺省實(shí)現(xiàn),而讓派生類(lèi)手動(dòng)確認(rèn)是否使用該默認(rèn)行為。

class Airplane {
public: virtual void fly(const Airport& dest) = 0;
protected: void defaultFly(const Airport& dest){ ... }
};

除了上面的方式處理impure virtual的缺省實(shí)現(xiàn),其實(shí)也可以將其轉(zhuǎn)換為:仍然使用pure virtual函數(shù)聲明接口,不同同時(shí)也提供其缺省定義,這樣派生類(lèi)在override這個(gè)pure virtual接口的時(shí)候既可以完全重新定義fly的行為,也可以直接一條語(yǔ)句用基類(lèi)名字直接調(diào)用基類(lèi)的缺省實(shí)現(xiàn)(Airplane::fly),其好處是不用引入一個(gè)新的函數(shù)名字,缺點(diǎn)是缺省實(shí)現(xiàn)成了public的了。

說(shuō)到此處,應(yīng)該對(duì)C++中接口繼承的行為得以了解了。

二、虛函數(shù)外的其他選擇

前面我們說(shuō)到了《C++之virtual函數(shù)訪問(wèn)性》中談及了NVI手法,算是對(duì)public virtual的一個(gè)強(qiáng)有力的替換工具,不過(guò)我們知道其本身也用到了虛函數(shù)。虛函數(shù)具有運(yùn)行時(shí)開(kāi)銷(xiāo),而且其實(shí)現(xiàn)也是編譯時(shí)間確定運(yùn)行時(shí)候選擇,在有些情況下其靈活性還是受限。

相比于虛函數(shù)依據(jù)派生類(lèi)型進(jìn)行行為的定制化之外,Strategy策略模式顯得更為的靈活。通過(guò)在對(duì)象內(nèi)部保存函數(shù)指針(或者更泛化的boost::function函數(shù)對(duì)象),其行為可以依據(jù)具體對(duì)象差異化而非派生類(lèi)型差異化,甚至通過(guò)Set接口其行為還可以在運(yùn)行時(shí)候進(jìn)行變更。雖然Meyers說(shuō)明如果使用非成員函數(shù),默認(rèn)將不能訪問(wèn)類(lèi)的私有成員,否則就需要對(duì)封裝性進(jìn)行一定程度的妥協(xié)松懈,但是通過(guò)boost::function+boost::bind這個(gè)強(qiáng)有力的工具,使用繼承體系中的成員函數(shù)也是十分方便的。

此處本人感覺(jué),雖然設(shè)計(jì)模式被奉為C++開(kāi)發(fā)的經(jīng)典,但是隨著Modern C++在標(biāo)準(zhǔn)上引入更多的特性和功能,C++的開(kāi)發(fā)將必定變的更加友好直觀,也不被過(guò)于墨守那古典23式了,畢竟絕大多數(shù)的設(shè)計(jì)模式都通過(guò)繼承來(lái)實(shí)現(xiàn)的,不可避免的增加了程序開(kāi)發(fā)和維護(hù)的復(fù)雜度。

三、繼承體系來(lái)的其他問(wèn)題

好了,輕松愉快的東西結(jié)束了,下面是C++史上的黑暗時(shí)刻了。

3.1 繼承而來(lái)名字的可見(jiàn)性

C++具有一套名字查找的規(guī)則,總體來(lái)說(shuō)就是從局部到外圍,從派生類(lèi)到基類(lèi),從內(nèi)層名字空間到全局名字空間的查找順序。

由于到此為止我們沒(méi)有說(shuō)明函數(shù)重載的情況,所以你此時(shí)仍然安之若素:對(duì)于public non-virtual函數(shù)我們不去重寫(xiě),對(duì)于virtual函數(shù)我們可以override,這一切安好,但是一旦考慮到相同函數(shù)名的重載問(wèn)題,C++有一套理論就會(huì)讓你暈乎了:C++防止在應(yīng)用程序庫(kù)或者應(yīng)用框架中建立的新的派生類(lèi)被附帶從疏遠(yuǎn)的基類(lèi)中繼承而來(lái)的重載函數(shù),所以在繼承的時(shí)候C++不會(huì)將基類(lèi)的名字自動(dòng)導(dǎo)入到派生類(lèi)中。

好了,這就說(shuō)明,之前繼承而來(lái)的接口,其實(shí)也是在使用的時(shí)候在派生類(lèi)作用域中沒(méi)有找到該符號(hào),而在基類(lèi)中找到該符號(hào)后滿(mǎn)足調(diào)用的,而如果你在基類(lèi)中定義了其某個(gè)重載版本(無(wú)論是virtual還是non-virtual)的時(shí)候,C++在名字查找的時(shí)候就在你的派生類(lèi)作用域中找到該名字了,然后進(jìn)行類(lèi)型檢查和重載,但是重載的版本只限于在派生類(lèi)中出現(xiàn)的版本,基類(lèi)的版本不參與重載?。?!

所以,在派生類(lèi)中想增加還是改寫(xiě)無(wú)論virtual還是non-virtual函數(shù)的重載版本,第一件事是使用using聲明將基類(lèi)符號(hào)的所有版本聲明到派生的名字作用域中,然后再干其他的。

3.2 絕不重新定義繼承而來(lái)的non-virtual函數(shù)

C++的non-virtual函數(shù)都是編譯期靜態(tài)綁定的,其名字查找從其指針的靜態(tài)類(lèi)型開(kāi)始。

任何情況下,都不要重新定義一個(gè)繼承而來(lái)的non-virtual函數(shù),否則其調(diào)用的版本決定于其指針靜態(tài)類(lèi)型,這與public繼承is-a的一致性關(guān)系相互違背。

3.3 絕不重新定義繼承來(lái)的缺省參數(shù)值

因?yàn)樯厦嬲f(shuō)到我們不應(yīng)該重新定義一個(gè)繼承而來(lái)的non-virtual函數(shù),所以到這里我們可以說(shuō):絕對(duì)不要重新定義一個(gè)繼承而來(lái)帶缺省參數(shù)值的virtual函數(shù)的參數(shù)默認(rèn)值。其原因是:virtual函數(shù)是動(dòng)態(tài)綁定的,而缺省參數(shù)是靜態(tài)綁定的。

所以如果基類(lèi)和派生類(lèi)的參數(shù)默認(rèn)值不一致,則使用引用、指針調(diào)用發(fā)生參數(shù)默認(rèn)值靜態(tài)綁定和調(diào)用函數(shù)體動(dòng)態(tài)綁定將會(huì)非常的詭異,所以需要避免這種情況。還有就是如果虛函數(shù)參數(shù)再基類(lèi)指定的參數(shù)缺省值,而派生類(lèi)override的時(shí)候沒(méi)有指明參數(shù)缺省值,此時(shí)如果客戶(hù)端以派生類(lèi)對(duì)象方式調(diào)用該函數(shù),則發(fā)生的是靜態(tài)綁定,需要顯示指定參數(shù)值;而如果客戶(hù)端以指針、引用的新式調(diào)用該函數(shù),則發(fā)生的是動(dòng)態(tài)綁定,可以不指定其帶有缺省值的參數(shù)。

class Shape {
public: virtual void draw(ShapeColor color = Red) const = 0; ...
};
class Circle: public Shape {
// 如果以對(duì)象模式調(diào)用draw,必須指定color參數(shù)而不能使用缺省參數(shù)
public: virtual void draw(ShapeColor color) const; ... 
};

解決這個(gè)問(wèn)題的一個(gè)方式是使用NVI手法,其public non-virtual接口提供默認(rèn)默認(rèn)值(且不會(huì)被派生類(lèi)重寫(xiě)),而private virtual不使用默認(rèn)默認(rèn)的特性以規(guī)避這種可能的不一致性。

3.4 private繼承

private繼承沒(méi)有”is-a”的契約關(guān)系了,在使用上一個(gè)巨大的差異是:編譯器不再會(huì)自動(dòng)將一個(gè)派生類(lèi)對(duì)象轉(zhuǎn)換為一個(gè)基類(lèi)對(duì)象了,這意味著原本接收基類(lèi)對(duì)象的函數(shù)參數(shù)將不再能夠?yàn)槠鋫鬟f派生類(lèi)對(duì)象作為實(shí)參了(對(duì)象、引用、指針類(lèi)型都不允許,編譯器會(huì)報(bào)基類(lèi)S是派生類(lèi)T不可訪問(wèn)的基類(lèi));同時(shí)由基類(lèi)繼承而來(lái)的所有成員,在派生類(lèi)中都會(huì)變成private的訪問(wèn)權(quán)限。

private繼承意味著只有實(shí)現(xiàn)部分被繼承,接口部分被全部略去了,所以private繼承應(yīng)當(dāng)是采用基類(lèi)的某些功能幫助派生類(lèi)完善其功能,從某種情況下說(shuō)具有”has-a”的符合類(lèi)型,所以除了考慮到派生類(lèi)需要訪問(wèn)基類(lèi)protected成員和virtual的因素被牽扯進(jìn)來(lái),否則應(yīng)該盡量使用組合類(lèi)型來(lái)代替private繼承,而且即使如此,也可以使用下面的手法瞞天過(guò)海:

class Timer { 
public: virtual void onTick() const; ... 
};
class Widget {
private:
 class WidgetTimer: public Timer {
 public: virtual void OnTick() const; ...
 };
 WidgetTimer timer;
};

關(guān)于protected繼承,連Meyers大神都沒(méi)用過(guò),那么我又何必廢腦經(jīng)去考慮他……

參考

Effective C++

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)我們的支持。

上一篇:c++ 寫(xiě)注冊(cè)表方式讓程序開(kāi)機(jī)自啟動(dòng)

欄    目:C語(yǔ)言

下一篇:快來(lái)領(lǐng)取!你想要的C++/C語(yǔ)言?xún)?yōu)秀書(shū)籍

本文標(biāo)題:關(guān)于C++面向?qū)ο笤O(shè)計(jì)的訪問(wèn)性問(wèn)題詳解

本文地址:http://www.jygsgssxh.com/a1/Cyuyan/1195.html

網(wǎng)頁(yè)制作CMS教程網(wǎng)絡(luò)編程軟件編程腳本語(yǔ)言數(shù)據(jù)庫(kù)服務(wù)器

如果侵犯了您的權(quán)利,請(qǐng)與我們聯(lián)系,我們將在24小時(shí)內(nèi)進(jìn)行處理、任何非本站因素導(dǎo)致的法律后果,本站均不負(fù)任何責(zé)任。

聯(lián)系QQ:835971066 | 郵箱:835971066#qq.com(#換成@)

Copyright © 2002-2020 腳本教程網(wǎng) 版權(quán)所有