編程
是藝術(shù),這無(wú)可否認(rèn)。不信的去看看高大爺?shù)臅兔靼琢?。藝術(shù)對(duì)于我們這些成天擠壓腦漿的程序員而言,是一味滋補(bǔ)的良藥。所以,在這個(gè)系列中,每一篇我打算
以藝術(shù)的形式開頭。???什么形式?當(dāng)然是最綜合的藝術(shù)形式。好吧好吧,就是歌劇。當(dāng)然,我沒(méi)辦法在一篇技術(shù)文章的開頭演出一整部歌劇,所以決定用一段詠嘆
調(diào)來(lái)作為開始。而且,還會(huì)盡量使詠嘆調(diào)同文章有那么一點(diǎn)關(guān)聯(lián),不管這關(guān)聯(lián)是不是牽強(qiáng)。
噢,我親愛的++
普契尼的獨(dú)幕歌劇歌劇《賈尼·斯基基》完成于1918年,同年初演于紐約。
本劇的劇情取自意大利詩(shī)人但丁(1265- 1321)的長(zhǎng)詩(shī)《神曲·地獄篇》中的一個(gè)故事:富商多納蒂死了。其遺囑內(nèi),將遺產(chǎn)全數(shù)捐獻(xiàn)給某一教堂。在場(chǎng)親友大失所望。眾人請(qǐng)賈尼·斯基基假扮多納蒂,騙過(guò)公證人,另立遺囑,遺產(chǎn)由眾親友均分。公證人到場(chǎng)。結(jié)果斯基基將少量遺產(chǎn)分與眾人,大部分留給了自己。遺囑錄畢,公證人離去。眾大嘩,斯基基從病榻躍起,持棒驅(qū)散眾人。
劇中斯基基的女兒勞蕾塔為表達(dá)對(duì)青年努奇奧的愛情,對(duì)其父唱起了這首美妙絕倫的詠嘆調(diào)——“我親愛的爸爸”:
“啊! 我親愛的爸爸,我愛那美麗少年。
我愿到露薩港去,買一個(gè)結(jié)婚戒指。
我無(wú)論如何要去,假如您不答應(yīng),
我就到威克橋上,縱身投入那河水里。
我多痛苦,我多悲傷。
啊! 天哪! 我寧愿死去!
爸爸,我懇求你!
爸爸,我懇求你!”
按照C/C++中對(duì)于后置操作符++的定義,操作數(shù)增加1,并返回原來(lái)的值。于是,有人根據(jù)這個(gè)給C++遍了一段笑話,流傳甚廣。那么,C++是否相對(duì)C加了那么一點(diǎn)點(diǎn),然后還是返回原來(lái)的值呢?那就讓我們來(lái)“實(shí)地考察”一下,了解這個(gè)++究竟加了多少。
我不打算羅列C++的各種紛繁復(fù)雜的特性。已經(jīng)有無(wú)數(shù)書籍文章做了這件事,肯定比我做的好得多。我要做的,是探索如何運(yùn)用C++的一些機(jī)制,讓我們能夠更方便、快捷、容錯(cuò)地開發(fā)軟件。這些特性很多都是非常簡(jiǎn)單的,基本的。正因?yàn)樗鼈兓?,很容易為人們所忽略。另一些則是高級(jí)的,需要多花些時(shí)間加以掌握的。但是,這些特性也具有一些簡(jiǎn)單,但卻非常實(shí)用、靈活和高效的用法。
相對(duì)于C,C++最主要的變化就是增加了類。嚴(yán)格地講,類是一種“用戶定義類型”,是擴(kuò)展類型系統(tǒng)的重要手段。類從本質(zhì)上來(lái)說(shuō),是一種ADT(Abstract Data Type,抽象數(shù)據(jù)類型)?;\統(tǒng)地講,ADT可以看作數(shù)據(jù)和作用在這些數(shù)據(jù)上的操作的集合。
類提供了一種特性,稱為可見性。意思是說(shuō),程序員可以按自己的要求,把類上的數(shù)據(jù)或函數(shù)隱藏起來(lái),不給其他人訪問(wèn)。于是,通過(guò)可見性的控制,可以讓一個(gè)類外部呈現(xiàn)一種“外觀”,而內(nèi)部可以使用任何可能的方法實(shí)現(xiàn)類的功能。這稱為“封裝”。
呵呵,聽煩了吧。這些東西是學(xué)過(guò)C++(或者任何時(shí)髦的OOP語(yǔ)言)的都已經(jīng)爛熟于胸了。這樣的話,我們就來(lái)點(diǎn)實(shí)際的,做個(gè)小案例,復(fù)習(xí)復(fù)習(xí)。溫故而知新嘛。:)
案例非常簡(jiǎn)單,做一個(gè)圓類。讓我們從“赤裸”的C結(jié)構(gòu)開始吧:
struct Cycle
{
float center_x;
float center_y;
float radius;
};
很傳統(tǒng)的表示,<圓心坐標(biāo),半徑>,便可以立刻定義出一個(gè)圓形。現(xiàn)在,假設(shè)我們需要計(jì)算圓形的面積。于是,我寫了一個(gè)函數(shù)執(zhí)行這項(xiàng)任務(wù):

float Area(const Cycle & rc) ...{
return PI*rc.radius*rc.radius;
}
很好。但是突然有一天,我心血來(lái)潮,把圓形類的存儲(chǔ)改成外切正方形的<左上角,右下角>形式,那么這個(gè)函數(shù)就不能用了。為了讓我這么一個(gè)三心二意的人能夠得到滿足,就得運(yùn)用封裝這個(gè)特性了:
class Cycle
{
public:
float get_center_x() { return left; }
float get_center_y() { return top; }
float get_radius() { return bottom; }
private:
float center_x;
float center_y;
float radius;
};
然后,面積計(jì)算公式稍作改動(dòng)就行了:
float Area(const Cycle & rc) {
return PI*rc. get_radius()*rc. get_radius();
}
這時(shí),如果我改變了Rectangle的數(shù)據(jù)存儲(chǔ)方式,也不會(huì)影響Area函數(shù):
class Cycle
{
public:
float get_center_x() { return (left+right)/2; }
float get_center_y() { return (top+bottom)/2; }
float get_radius() { return (right-left)/2; }
private:
float left;
float top;
float right;
float bottom;
};
運(yùn)用了封裝之后,類的實(shí)現(xiàn)和接口分離了。于是我們便可以在使用方神不知鬼不覺的情況下,改變我們的實(shí)現(xiàn),以獲得更好的利益,比如效率的提升、代碼維護(hù)性的提高等等。
當(dāng)我們嘗到封裝的甜頭之后,便會(huì)繼續(xù)發(fā)揚(yáng)光大:
class Cycle
{
public:
float get_center_x() { return left; }
float get_center_y() { return top; }
float get_radius() { return bottom; }
float get_left() { return center_x-radius; }
float get_right() { return center_x+radius; }
float get_top() { return center_y-radius; }
float get_bottom() { return center_y+radius; }
float area() { return PI*get_radius()*get_radius(); }
private:
…
};
作為一個(gè)思想純正的OOP程序員而言,這是一個(gè)漂亮的設(shè)計(jì)。不過(guò),對(duì)于我這樣一個(gè)同樣思想純正的Multiple-paradigm程序員而言,這是個(gè)不恰當(dāng)?shù)脑O(shè)計(jì)。
我承認(rèn),這個(gè)設(shè)計(jì)完成了工作,達(dá)到了設(shè)計(jì)目標(biāo)。但是,這種被Herb Sutter稱為“單片式”的設(shè)計(jì)是一種典型的過(guò)度OO的行為。Sutter在他的《Exceptional C++ Style》一書中,用了最后四個(gè)條款,詳細(xì)地批判了以std::string為代表的這種設(shè)計(jì)。
這里,沒(méi)有那么復(fù)雜的案例,我就簡(jiǎn)單地介紹其中存在的一些問(wèn)題,其余的,請(qǐng)看Sutter的書。首先,當(dāng)Cycle的內(nèi)部存儲(chǔ)形式發(fā)生變化時(shí),需要修改不只一個(gè)地方:
class Cycle
{
public:
float get_center_x() { return (left+right)/2; }
float get_center_y() { return (top+bottom)/2; }
float get_radius() { return (right-left)/2; }
float get_left() { return left; }
float get_right() { return top; }
float get_top() { return right; }
float get_bottom() { return bottom; }
private:
float left;
float top;
float right;
float bottom;
};
當(dāng)然,如果get_left()等成員函數(shù)通過(guò)get_center_x()等成員函數(shù)計(jì)算獲得:

float get_left() ...{ return get_center_x()-get_radius(); }
這樣在改變數(shù)據(jù)存儲(chǔ)的情況下,修改get_left()等函數(shù)了。不過(guò),get_center_x()等函數(shù)本來(lái)就是從left等成員數(shù)據(jù)上計(jì)算獲得,get_left()再逆向計(jì)算回去,顯得有些奇怪。
這還只是小問(wèn)題。更重要的是增加了這些冗余的函數(shù),使得類在接口的靈活性上變差。假設(shè)我們?cè)?/span>Cycle類上增加一個(gè)offset()函數(shù),實(shí)現(xiàn)平移:
class Cycle
{
public:
…
void offset(point o) { center_x+=x; center_y+=y; }
…
};
在Cycle的使用代碼中,調(diào)用了offset():
Cycle c;
…
c.offset(20, 100);
假設(shè),此時(shí)來(lái)了一個(gè)需求,要求offset()可以接受size類的對(duì)象作為參數(shù)。那么就必須修改Cycle類的定義,改變或重載offset()。如果這個(gè)Cycle是別人寫的,不是我們所能改變的,那么事情就比較麻煩。
按照現(xiàn)代的Multiple-paradigm的設(shè)計(jì)理念,這類操作應(yīng)當(dāng)以non-member non-friend的形式出現(xiàn),而類僅僅保持最小的、無(wú)冗余的接口集合:
void offset(Cycle& c, point o) {
c.set_center_x(c.get_center_x()+o.x); //如果有屬性,就更好了:)
c.set_center_y(c.get_center_y()+o.y);
}
此時(shí),如果需求改變,那么只需編寫一個(gè)函數(shù)重載,便可以解決問(wèn)題,而無(wú)需考慮類的修改了。
關(guān)于這方面的問(wèn)題,Meyes有一篇很有見地的文章:http://www.ddj.com/cpp/184401197。作者認(rèn)為,冗余的成員函數(shù)實(shí)際上只會(huì)降低類的封裝性,而不是提高。這看似一個(gè)嘩眾取寵的論點(diǎn),但是Meyes所給出的論據(jù)卻非常具有吸引力。他給出了一個(gè)“封裝性”的具體度量:封裝性的好壞取決于類實(shí)現(xiàn)變化時(shí),對(duì)使用代碼產(chǎn)生的影響。類的接口的冗余度越大,越容易受到實(shí)現(xiàn)變化的影響。
所以,現(xiàn)在主流的C++社群都提倡用小類+non-member non-friend函數(shù)實(shí)現(xiàn),以提高靈活性。這一點(diǎn)反過(guò)來(lái)也更接近計(jì)算機(jī)軟件“數(shù)據(jù)+操作”的本質(zhì)。
經(jīng)過(guò)長(zhǎng)時(shí)間的開發(fā)工作,我們逐步積累起很多圓類,都是面向不同實(shí)現(xiàn)。有的通過(guò)傳統(tǒng)的<圓心,半徑>存放數(shù)據(jù);有的通過(guò)外接正方形坐標(biāo)保存數(shù)據(jù);有的通過(guò)一個(gè)長(zhǎng)軸等于短軸的橢圓存放數(shù)據(jù);有的通過(guò)內(nèi)接正方形保存數(shù)據(jù);…。不過(guò)它們的接口都是相同的,即<圓心,半徑>形式。
面對(duì)這些圓的實(shí)現(xiàn),為它們各自開發(fā)一套算法實(shí)在讓人泄氣。大量的重復(fù)代碼,和重復(fù)勞動(dòng),簡(jiǎn)直是對(duì)程序員的智慧的侮辱。我們需要開發(fā)一套算法,然后用于所有圓類。這就需要?jiǎng)佑?/span>C++的MDW(大規(guī)模殺傷性武器)——模板:
template<typename T>
void offset(T& c, float x, float y) {
c.set_center_x(c.get_center_x()+o.x);
c.set_center_y(c.get_center_y()+o.y);
}
這樣,同一個(gè)算法便可以用于(我們)所有的圓類:
CycleA c1;
CycleB c2;
CycleC c3;
…
offset(c1, 10, 20);
offset(c2, 2, 700);
offset(c3, 99, 9);
…
不過(guò),有些頑固的人認(rèn)定一個(gè)圓應(yīng)當(dāng)用外接正方形的形式定義(接口形式是外接正方形的坐標(biāo))。并且基于這種構(gòu)造,開發(fā)了一堆有用的函數(shù)模板。比如說(shuō)inflate<>()。
可我們這些理智的人已經(jīng)開發(fā)了<圓心,半徑>形式的Cycle。只是看中了頑固派的哪些操作函數(shù),希望能夠重用一下,免得自己重復(fù)勞動(dòng)。同時(shí),我們又不希望重做一個(gè)Cycle類,來(lái)符合那些缺乏理智的Cycle定義。
怎么辦?設(shè)計(jì)模式告訴我們,可以用Adapter解決問(wèn)題:
class CycleAdapter
{
public:
CycleAdapter(Ours::Cycle const& c) : m_cycle(c){}
float get_left() { return Ours::getLeft(m_cycle); }
void set_left(float left) { return Ours::setLeft(m_cycle, left); }
…
private:
Cycle& m_cycle;
};
此后,我們便可以使用頑固派的函數(shù)了:
Ours::Cycle c;
…
CycleAdapter ca(c);
Theirs::inflate(ca, 1.5);
唉,世事難料,上頭下命令,必須同時(shí)使用我們自己的圓類和頑固派的圓類。(肯定是收了他們的好處了)。沒(méi)辦法,命令終究是命令??蓮慕裢?,我們就得同時(shí)開發(fā)兩套算法。痛苦。不過(guò)相比使用算法的人來(lái)說(shuō),我們還算幸運(yùn)的。他們必須不斷地在Ours和Theirs命名空間里跳來(lái)跳去,時(shí)間長(zhǎng)了難保不出錯(cuò)。
算法使用者希望一個(gè)算法就是一個(gè)名字,在同一個(gè)命名空間,以免混亂。幸運(yùn)的是,在一種未來(lái)技術(shù)的支持下,我們做到了。這就是C++的BM(彈道導(dǎo)彈,MDW的運(yùn)載器)——concept:
concept OurCycle<typename T> {
float T::get_left();
void T::set_left(float left);
…
}
concept TheirCycle<typename T> {
float T::get_left();
void T::set_left(float left);
…
}
concept_map OurCycle<Ours::CycleA>;
concept_map OurCycle<Ours::CycleB>;
…
concept_map TheirCycle<Theirs::CycleA>;
concept_map TheirCycle<Theirs::CycleB>;
…
template<OurCycle T> void move(T& c, point const& p) {…} //#1
template<TheirCycle T> void move(T& c, point const& p) {…} //#2
…
在concept和特化的共同作用下,我們便可以很方便地(不需考慮我們的,還是他們的)使用這些算法了:
Ours::CycleA c1;
Ours::CycleB c2;
Theirs::CycleA c3;
…
move(c1, point(20,3)); //調(diào)用#1
move(c2, point(5, 111)); //調(diào)用#1
move(c3, point(7, 22)); //調(diào)用#2
隨著應(yīng)用的發(fā)展,我們不僅僅需要操作一個(gè)圖形,還要把它畫出來(lái)。這件事不算難。但是,面對(duì)不同的需求,我們有完全不同的兩套方案。
先看一下常見的方案——OOP。這是經(jīng)典的OOP案例,我就簡(jiǎn)單地描述一下,諸位別嫌我羅嗦J。為了方便,這里用mfc作為繪圖平臺(tái),盡管我討厭mfc。
定義一個(gè)抽象類:
class Graph
{
…
public:
virtual void Draw(CDC& dc)=0;
};
所有圖形類從Graph繼承而來(lái),并且重寫(override)Draw():
class Cycle : public Graph
{
…
public:
void Draw(CDC& dc) {
… //繪制圓
}
};
class Rectangle : public Graph
{
…
public:
void Draw(CDC& dc) {
… //繪制矩形
}
};
…
此后,便可以創(chuàng)建一個(gè)對(duì)象并繪制:
但這同不用虛函數(shù)有什么區(qū)別?請(qǐng)看以下代碼:
typedef shared_ptr<Graph> GraphPtr;
vector<GraphPtr> gv;
gv.push_back(GraphPtr(new Cycle));
gv.push_back(GraphPtr(new Rectangle));
gv.push_back(GraphPtr(new Line));
…
for_each(gv.begin(), gv.end(), mem_fun(&Graph::Draw));
(附注:我這里不辭辛勞地用了智能指針,為的是無(wú)憂無(wú)慮地編寫代碼,不必為資源的安全而煩惱。同時(shí),標(biāo)準(zhǔn)算法for_each和成員函數(shù)適配器mem_fun的使用也是為了獲得更簡(jiǎn)潔、更可靠的代碼。這些都是應(yīng)當(dāng)廣泛推薦的做法,特別是初學(xué)者)。
拋開智能指針,gv中包含的是基類Graph的指針,當(dāng)各種繼承自Graph的對(duì)象插入gv時(shí),多態(tài)地轉(zhuǎn)換成基類Graph的指針。當(dāng)后面for_each算法執(zhí)行時(shí),它會(huì)依次取出gv的每一個(gè)元素,并通過(guò)mem_fun適配器調(diào)用每個(gè)元素(即Graph指針)上的Draw成員函數(shù)。(關(guān)于for_each和mem_fun的奇妙原理,我這里就不說(shuō)了,有很多參考書都有很詳細(xì)的解釋,比如《C++ STL》、《C++ Standard Library》等等)。
這里的核心在于,當(dāng)我們調(diào)用Graph指針上的Draw成員函數(shù)時(shí),實(shí)際上被轉(zhuǎn)而定向到繼承類(Cycle、Rectangle等)的Draw()成員函數(shù)上。這個(gè)功能非常有用,也就是說(shuō),當(dāng)一組類(Cycle等)繼承自同一個(gè)基類(Graph)后,可以通過(guò)覆蓋基類上的虛函數(shù)(Draw)實(shí)現(xiàn)對(duì)基類行為的修改和擴(kuò)充。同時(shí),基類(Graph)成為了繼承類(Cycle等)的共同接口,通過(guò)接口我們可以將不同類型的對(duì)象放在同一個(gè)容器中。這種技術(shù)可以避免大量switch/case的硬編碼分支代碼,(也稱為tag dispatch),大大簡(jiǎn)化我們軟件的構(gòu)架。同時(shí)也可以大幅提高性能,操作分派可以從O(n)復(fù)雜度變成O(1)(hash_map)或O(logN)(map)。
這種通常被稱為“動(dòng)多態(tài)”的OOP機(jī)制,允許我們?cè)谶\(yùn)行時(shí),根據(jù)某些輸入,比如從一個(gè)圖形腳本文件中讀取圖形數(shù)據(jù),創(chuàng)建對(duì)象,并統(tǒng)一存放在唯一容器中,所有圖形對(duì)象都以一致的方式處理,極大地優(yōu)化了體系結(jié)構(gòu)。
有“動(dòng)”必有“靜”。既然有“動(dòng)多態(tài)”,就有“靜多態(tài)”。所謂“靜多態(tài)”是指模板(或泛型)帶來(lái)的一種多態(tài)行為。關(guān)于模板前面我們已經(jīng)小有嘗試,現(xiàn)在我們通過(guò)模板上的一些特殊機(jī)制,來(lái)實(shí)現(xiàn)一種多態(tài)行為。
作為獨(dú)立于OOP的一種新的(其實(shí)也不怎么新,其理論根源可以追溯到1967年以前)范式,模板(泛型)相關(guān)的編程被稱為“泛型編程”(GP)。gp最常用的一種風(fēng)格就是算法獨(dú)立于類,這在前面我們已經(jīng)看到過(guò)了。所以,這里的Draw也作為自由函數(shù)模板:
template<typename T> void Draw(CDC& dc, T& g);
template<> void Draw<Cycle>(CDC& dc, Cycle& g) { //#1
dc. Ellipse(get_left(g), get_top(g), get_right(g), get_bottom(g));
}
template<> void Draw<Rectangle>(CDC& dc, Rectangle& g) { //#2
dc. Rectangle(get_left(g), get_top(g), get_right(g), get_bottom(g));
}
…
當(dāng)用不同的圖形對(duì)象調(diào)用Draw時(shí),編譯器會(huì)自動(dòng)匹配不同的版本:
Cycle c;
Rectangle r;
…
Draw(dc, c); //#1
Draw(dc, r); //#2
這里使用了函數(shù)模板特化這種特性,促使編譯器在編譯時(shí)即根據(jù)特化的情況調(diào)用合適的函數(shù)模板版本。不過(guò),仔細(xì)看函數(shù)模板的聲明,會(huì)發(fā)現(xiàn)這同函數(shù)的重載幾乎一樣。實(shí)際上此時(shí)使用函數(shù)重載更加恰當(dāng)。(函數(shù)重載通常也被認(rèn)為是一種多態(tài))。這里使用模板,是為了引出未來(lái)的concept的方案:
template<OurCycle T> void Draw(CDC& dc, T& g) { //#1
dc. Ellipse(get_left(g), get_top(g), get_right(g), get_bottom(g));
}
template<OurRectangle T> void Draw(CDC& dc, T& g) { //#2
dc. Rectangle(get_left(g), get_top(g), get_right(g), get_bottom(g));
}
同前面的move模板一樣,這里的Draw也實(shí)現(xiàn)了編譯期的操作分派(以類型為tag)。此時(shí),我們便可以看出,引入了concept之后,模板的特化(針對(duì)concept)不僅僅使得代碼重用率提高,而且其形式同函數(shù)重載更加類似。也就是說(shuō),重載多態(tài)和函數(shù)模板的靜多態(tài)有了相同的含義(語(yǔ)義),兩者趨向于統(tǒng)一。
以上代碼另一個(gè)值得注意的地方是get_left()等函數(shù)。這些函數(shù)實(shí)際上是函數(shù)模板,分別針對(duì)不同的類型特化。這使得所有相同語(yǔ)義的操作,都以同樣的形式表現(xiàn)。對(duì)于優(yōu)化開發(fā),提高效率,這種形式具有非常重要的作用。
模板的這種靜多態(tài)同OOP的動(dòng)多態(tài)有著完全不同的應(yīng)用領(lǐng)域。更重要的是,兩者是互補(bǔ)的。前者是編譯時(shí)執(zhí)行的多態(tài),具有很高的靈活性、擴(kuò)展性和運(yùn)行效率;后者是運(yùn)行時(shí)執(zhí)行的多態(tài),具備隨機(jī)應(yīng)變的響應(yīng)特性。所以,通常情況下,凡是能在開發(fā)時(shí)確定的多態(tài)形式,比如上述代碼中get_left是可以在編譯時(shí)明確調(diào)用版本,適合使用模板。反之,只能在運(yùn)行時(shí)確定的多態(tài)行為,比如從圖形腳本文件中讀取的圖形數(shù)據(jù),則應(yīng)當(dāng)使用OOP。
最后,這里還將涉及一種非常簡(jiǎn)單,但卻極其實(shí)用的C++特性:RAII。所謂RAII,是Bjarne為一種資源管理形式所起的笨拙的名字,全稱是Resource Acquisition Is Initialization。其實(shí)這個(gè)名稱并不能表達(dá)這種技術(shù)的特征。簡(jiǎn)單地講,就是在構(gòu)造函數(shù)中分配資源,在析構(gòu)函數(shù)中加以釋放。由于C++的自動(dòng)對(duì)象,包括棧對(duì)象、一個(gè)對(duì)象的子對(duì)象等等,在對(duì)象生成和初始化時(shí)調(diào)用構(gòu)造函數(shù),在對(duì)象生命期結(jié)束時(shí)調(diào)用析構(gòu)函數(shù)。所以,RAII這種資源管理形式是自動(dòng)的和隱含的。下面用文件句柄來(lái)做一個(gè)說(shuō)明:
class file
{
public:
file(string const& fn) {
m_hFile=open_file(fn.c_str());//假設(shè)open_file是C函數(shù),close也一樣
if(0==m_hFile)
throw exception(“open file failed!”);
}
~file() {
close(m_hFile);
}
private:
handle m_hFile;
};
在一個(gè)函數(shù)中,當(dāng)我們使用這個(gè)類時(shí),可以無(wú)需考慮如何獲取和釋放資源,同時(shí)也保證了異常的安全:
void fun() {
file f(“x.txt”);
… //利用f進(jìn)行操作,可能會(huì)拋出異常
} //當(dāng)函數(shù)返回或異常拋出,棧清理的時(shí)候,會(huì)自動(dòng)調(diào)用file::~file(),釋放文件句柄
這樣,資源管理會(huì)變得非常簡(jiǎn)單、方便,即便是最鐵桿的C程序員,也能從中獲得很大的好處。
而且,RAII不僅僅可以用來(lái)管理資源,還可以管理任何類似資源的東西(也就是有借有還的東西)。我們還是拿繪圖作為案例。
用過(guò)mfc的都知道,有時(shí)我們需要改變dc的設(shè)置,比如pen的寬度、brush的顏色等等,在繪圖完成之后在回到原來(lái)的設(shè)置。mfc(確切地說(shuō)是Win32)提供了一對(duì)函數(shù),允許我們把原先的dc設(shè)置保存下來(lái),在完成繪圖后在恢復(fù):
void Draw(CDC& dc, Cycle& c) {
int old_dc=dc.SaveDC();
… //使用dc,可能拋異常
dc.RestoreDC(old_dc);
}
這種“赤裸裸”地使用Save/RestoreDC并非是件好事,程序員可能忘記調(diào)用RestoreDC返回原來(lái)狀態(tài),或者程序拋出異常,使得dc沒(méi)機(jī)會(huì)Restore。利用RAII,我們便可以很優(yōu)雅地解決這類問(wèn)題:
class StoreDC
{
public:
StoreDC(CDC& dc): m_dc(dc) {
m_stored=m_dc.SaveDC();
if(0==m_stored)
throw exception(“DC is not saved.”);
}
~StoreDC() {
m_dc.RestoreDC(m_stored);
}
private:
CDC& m_dc;
int m_stored;
};
此后,可以很簡(jiǎn)單地處理dc的Restore問(wèn)題:
void Draw(CDC& dc, Cycle& c) {
StoreDC sd(dc);
… //使用dc,可能拋異常
} //函數(shù)結(jié)束時(shí),會(huì)自動(dòng)RestoreDC,無(wú)論正常退出還是拋出異常
除此以外,RAII還可以用于維持commit or rollback語(yǔ)義等等方面。關(guān)于這些內(nèi)容,可以參考一本非常實(shí)用的書:《Imperfect C++》。
C++擁有很多非常好的和實(shí)用的機(jī)制,限于篇幅(以及我未來(lái)的文章J)只能就此打住。這里我蜻蜓點(diǎn)水般的掃描了一下C++的一些主要的特性,意圖告訴大家,如果你覺得C++并沒(méi)有加多少,那么還是請(qǐng)認(rèn)真地了解一下真正的C++。盡管C++在這些特性之外,存在很多弊病,并非那么容易掌握。但是,了解這些基本的特性,對(duì)于程序員,無(wú)論是否使用C++,都有非常大的幫助。
最近,一次普通的開發(fā)活動(dòng),讓我突然意識(shí)到其實(shí)有很多實(shí)際開發(fā)中的問(wèn)題,還是仰賴一些非?;A(chǔ)和簡(jiǎn)單的特性。關(guān)鍵在于如何認(rèn)識(shí)和正確使用這些特性。于是,我開始漸漸地將一部分目光從高深的技術(shù)和特性轉(zhuǎn)向如何更好地使用這些基本特性。從這一點(diǎn)上來(lái)看,C++社群需要Abrams、Alexandrescu這類牛人,但更需要Matthew Wilson這樣的實(shí)踐者。大多數(shù)情況下,Matthew這樣的實(shí)踐牛人對(duì)于整個(gè)社群更重要。
對(duì)于C++的各種負(fù)面誤解可能并不會(huì)對(duì)C++產(chǎn)生實(shí)質(zhì)性的傷害。而傷害最大的,反而是過(guò)度宣揚(yáng)和不切實(shí)際地推廣那些極端機(jī)巧的技術(shù)和方法。這些技術(shù)的作用無(wú)可否認(rèn),但在尚未掌握C++基本使用技能的人群中推廣,就好比教小學(xué)生寫學(xué)術(shù)論文那樣不切實(shí)際。結(jié)果很容易造成,要么鉆入技術(shù)牛角尖,要么被嚇跑。
總之一句話,基礎(chǔ)更重要。