我從來(lái)沒(méi)有在別的語(yǔ)言的粉里面看見(jiàn)過(guò)這么容易展示人性丑陋一面的粉,就算是從十幾年前開(kāi)始的C++和C對(duì)噴,GC和非GC對(duì)噴,靜態(tài)類(lèi)型動(dòng)態(tài)類(lèi)型對(duì)噴的時(shí)候,甚至是云風(fēng)出來(lái)噴C++黑得那么驚天動(dòng)地的時(shí)候,都沒(méi)有發(fā)生過(guò)這么腦殘的事情。這種事情只發(fā)生在go語(yǔ)言的腦殘粉的身上,這究竟代表什么呢?想學(xué)go語(yǔ)言的人最好小心一點(diǎn)了,學(xué)怎么用go沒(méi)關(guān)系,go學(xué)成了因?yàn)槭懿涣颂絼e的語(yǔ)言去也沒(méi)關(guān)系,就算是抖M很喜歡被折騰所以堅(jiān)持用go也沒(méi)關(guān)系,但是把自己學(xué)成了腦殘粉,自己的心智發(fā)生不可逆轉(zhuǎn)的變換,那就不好了。
當(dāng)然,上一篇文章最后那個(gè)例子應(yīng)該是我還沒(méi)說(shuō)清楚,所以有些人有這種“加上一個(gè)虛析構(gòu)函數(shù)就可以了”的錯(cuò)覺(jué)也是情有可原的。Base* base = new Derived;之后你去delete沒(méi)問(wèn)題,是因?yàn)槲鰳?gòu)函數(shù)你還可以聲明成虛的。但是Base* base = new Derived[10];之后你去delete[]發(fā)生了問(wèn)題,是因?yàn)镈erived和Base的長(zhǎng)度不一樣,所以當(dāng)你開(kāi)始試圖計(jì)算&base[1]的時(shí)候,你實(shí)際上是拿到了第一個(gè)Derived對(duì)象的中間的一個(gè)位置,根本不是第二個(gè)Derived。這個(gè)時(shí)候你在上面做各種操作(譬如調(diào)用析構(gòu)函數(shù)),你連正確的this指針都拿不到,你再怎么虛也是沒(méi)用的。不過(guò)VC++單純做delete[]的話,在這種情況下是不會(huì)有問(wèn)題的,我猜它內(nèi)部不僅記錄了數(shù)組的長(zhǎng)度,還記錄了每一個(gè)元素的尺寸。當(dāng)然,你直接用bases[1]->DoSomething()的時(shí)候,出事是必須的。
所以今天粉絲群在討論昨天的這個(gè)例子的時(shí)候,我們的其中一位菊苣就說(shuō)了一句話:
當(dāng)你使用C++的時(shí)候C的一部分子集最好少碰
我也很贊同。反正C++已經(jīng)有各種內(nèi)置類(lèi)型了,譬如typeid出來(lái)的按個(gè)東西(我給忘了)啊,initialization_list啊,range什么的。為什么就不給new T[x]創(chuàng)建一個(gè)類(lèi)型呢?不過(guò)反正都已經(jīng)成為現(xiàn)實(shí)了,沒(méi)事就多用用vector和shared_ptr吧,不要想著什么自己new自己delete了。
今天我們來(lái)講一個(gè)稍微“高級(jí)”一點(diǎn)點(diǎn)的坑。這是我在工作之后遇到的一個(gè)現(xiàn)實(shí)的例子。當(dāng)然,語(yǔ)言的坑都擺在那里,人往坑里面跳都肯定是因?yàn)樽约褐赖臇|西還不夠多造成的。但是坑有三種,第一種是很明顯的,只要遵守一些看起來(lái)很愚蠢但是卻很有效的原則(譬如說(shuō)if(1 == a)…)就可以去除的。第二種坑是因?yàn)槟悴恢酪恍└呒?jí)的知識(shí)(譬如說(shuō)lambda和變量揉在一起的生命周期的事情)從而跳坑的。第三種純粹就是由于遠(yuǎn)見(jiàn)不夠了——譬如說(shuō)下面的例子。
在春光明媚的一個(gè)早上,我接到了一個(gè)新任務(wù),要跟另一個(gè)不是我們組的人一起寫(xiě)一個(gè)圖像處理的pipeline的東西。這種pipeline的節(jié)點(diǎn)無(wú)非就是什么直方圖啊,卷積啊,灰度還有取邊緣什么的。于是第一天開(kāi)會(huì)的時(shí)候,我拿到了一份spec,上面寫(xiě)好了他們?cè)O(shè)計(jì)好但是還沒(méi)開(kāi)始寫(xiě)的C++的interface(沒(méi)錯(cuò),就是那種就算只有一個(gè)實(shí)現(xiàn)也要用interface的那種流派),讓我回去看一看,過(guò)幾天跟他們一起把這個(gè)東西實(shí)現(xiàn)出來(lái)。當(dāng)然,這些interface里面肯定會(huì)有矩陣:
template<typename T>
class IMatrix
{
public:
virtual ~IMatrix(){}
virtual T* GetData()=0;
virtual int GetRows()=0;
virtual int GetColumns()=0;
virtual int GetStride()=0;
virtual T Get(int r, int c)=0;
virtual void Set(int r, int c, T t)=0;
};
其實(shí)說(shuō)實(shí)話,IMatrix這么寫(xiě)的確沒(méi)什么大問(wèn)題。于是我們就很愉快的工作了幾天,然后把這些純粹跟數(shù)學(xué)有關(guān)的算法都完成了,然后就開(kāi)始做卷積的事情了。卷積所需要的那一堆數(shù)字其實(shí)說(shuō)白了他不是矩陣,但因?yàn)闉檫@種東西專(zhuān)門(mén)做一個(gè)類(lèi)也沒(méi)意義,所以我們就用行列一樣多的矩陣來(lái)當(dāng)filter。一開(kāi)始的接口定義成這個(gè)樣子,因?yàn)镮Bitmap可能有不同的儲(chǔ)存方法,所以如何做卷積其實(shí)只有IBitmap的實(shí)現(xiàn)自己才知道:
template<typename TChannel>
class IBitmap
{
......
virtual void Apply(IMatrix<float>& filter)=0;
......
};
于是我們又愉快的度過(guò)了幾天,直到有一天有個(gè)人跳出來(lái)說(shuō):“Apply里面又不能修改filter,為什么不給他做成const的?”于是他給我們展示了他修改后的接口:
template<typename TChannel>
class IBitmap
{
......
virtual void Apply(IMatrix<const float>& filter)=0;
......
};
我依稀還記得我當(dāng)時(shí)的表情就是這樣子的→囧。
語(yǔ)言的類(lèi)型系統(tǒng)是一件特別復(fù)雜的事情,特別是像C++這種,const T<a, b, c>和T<const a, const b, cont c>是兩個(gè)不一樣的類(lèi)型的。一們語(yǔ)言,凡是跟優(yōu)美的理論每一個(gè)不一致的地方都是一個(gè)坑,區(qū)別只是有些坑嚴(yán)重有些坑不嚴(yán)重。當(dāng)然上面這個(gè)不是什么大問(wèn)題,因?yàn)檎娴陌凑者@個(gè)接口寫(xiě)下去,最后會(huì)因?yàn)榘l(fā)現(xiàn)創(chuàng)建不了IMatrix<const float>的實(shí)現(xiàn)而作罷。
而原因很簡(jiǎn)單,因?yàn)橐话銇?lái)說(shuō)IMatrix<T>的實(shí)現(xiàn)內(nèi)部都有一個(gè)T*代表的數(shù)組。這個(gè)時(shí)候給你換成了const float,你會(huì)發(fā)現(xiàn),你的Set函數(shù)在也沒(méi)辦法把const float寫(xiě)進(jìn)const float*了,然后就掛了。所以正確的方法當(dāng)然是:
virtual void Apply(const IMatrix<float>& filter)=0;
不過(guò)在展開(kāi)這個(gè)問(wèn)題之前,我們先來(lái)看一個(gè)更加淺顯易懂的“坑”,是關(guān)于C#的值類(lèi)型的。譬如說(shuō)我們有一天需要做一個(gè)超高性能的包含四大力學(xué)的粒子運(yùn)動(dòng)模擬程序——咳咳——總之從一個(gè)Point類(lèi)型開(kāi)始。一開(kāi)始是這么寫(xiě)的(C# 5.0):
struct Point
{
public int x;
public int y;
}
var ps = new Point[] { new Point { x = 1, y = 2 } };
ps[0].x = 3;
已開(kāi)始運(yùn)作的很好,什么事情都沒(méi)有發(fā)生,ps[0]里面的Point也被很好的更改了。但是有一天,情況變了,粒子之間會(huì)開(kāi)始產(chǎn)生和消滅新的粒子了,于是我把數(shù)組改成了List:
var ps = new List<Point> { new Point { x = 1, y = 2 } };
ps[0].x = 3;
結(jié)果編譯器告訴我最后一行出了一個(gè)錯(cuò)誤:
Cannot modify the return value of 'System.Collections.Generic.List<ArrayTest2.Program.Point>.this[int]' because it is not a variable
C#這語(yǔ)言就是牛逼啊,我用了這么久,就只找出這個(gè)“不起眼的問(wèn)題”的同時(shí),還是一個(gè)編譯錯(cuò)誤,所以用C#的時(shí)候根本沒(méi)有辦法用錯(cuò)啊。不過(guò)想想,VB以前這么多人用,除了on error resume next以外也沒(méi)用出什么坑,可見(jiàn)Microsoft設(shè)計(jì)語(yǔ)言的功力比某狗公司那是要強(qiáng)多了。
于是我當(dāng)時(shí)就覺(jué)得很困惑,隨手寫(xiě)了另一個(gè)類(lèi)來(lái)驗(yàn)證這個(gè)問(wèn)題:
class PointBox
{
public int Number { get; set; }
public Point Point { get; set; }
}
var box = new PointBox() { Number = 1, Point = new Point { x = 1, y = 2 } };
box.Number += 3;
box.Point.x = 5;
結(jié)果倒數(shù)第二行過(guò)了,倒數(shù)第一行還是編譯錯(cuò)誤了。為什么同樣是屬性,int就可以+=3,Point就不能改一個(gè)field非得創(chuàng)建一個(gè)新的然后再?gòu)?fù)制進(jìn)去呢?后來(lái)只能得到一個(gè)結(jié)論,數(shù)組可以List不可以,屬性可以+=不能改field(你給Point定義一個(gè)operator+,那你對(duì)box.Point做+=也是可以的),只能認(rèn)為是語(yǔ)言故意這么設(shè)計(jì)的了。
寫(xiě)到這里,我想起以前在MSDN上看過(guò)的一句話,說(shuō)一個(gè)結(jié)構(gòu),如果超過(guò)了16個(gè)字節(jié),就建議最好不要做成struct。而且以前老趙寫(xiě)了一個(gè)小sample也證明大部分情況下用struct其實(shí)還不如用class快。當(dāng)然至于是為什么我這里就不詳細(xì)展開(kāi)了,我們來(lái)講語(yǔ)法上的問(wèn)題。
在C#里面,struct和class的區(qū)別,就是值和引用的區(qū)別。C#專(zhuān)門(mén)做了值類(lèi)型和引用類(lèi)型,值類(lèi)型不能轉(zhuǎn)成引用(除非box成object或nullable或lazy等),引用類(lèi)型不能轉(zhuǎn)值類(lèi)型。值不可以繼承,引用可以繼承。我們都知道,你一個(gè)類(lèi)繼承自另一個(gè)類(lèi),目的說(shuō)到底都是為了覆蓋幾個(gè)虛函數(shù)。如果你不是為了覆蓋虛函數(shù)然后你還要繼承,八成是你的想法有問(wèn)題。如果繼承了,你就可以從子類(lèi)的引用隱式轉(zhuǎn)換成父類(lèi)的引用,然后滿足里氏代換原則。
但是C#的struct是值類(lèi)型,也就是說(shuō)他不是個(gè)引用(指針),所以根本不存在什么拿到父類(lèi)引用的這個(gè)事情。既然你每一次見(jiàn)到的類(lèi)型都是他真正的類(lèi)型(而不像class,你拿到IEnumerable<T>,他可能是個(gè)List<T>),那也沒(méi)有什么必要有虛函數(shù)了。如果你在struct里面不能寫(xiě)虛函數(shù),那還要繼承干什么呢?所以struct就不能繼承。
然后我們來(lái)看一看C#的屬性。其實(shí)C#的operator[]不是一個(gè)操作符,跟C++不一樣,他是當(dāng)成屬性來(lái)看待的。屬性其實(shí)是一個(gè)語(yǔ)法糖,其中的getter和setter是兩個(gè)函數(shù)。所以如果一個(gè)屬性的類(lèi)型是struct,那么getter的返回值也是struct。一個(gè)函數(shù)返回struct是什么意思呢?當(dāng)然是把結(jié)果【復(fù)制】一遍然后返回出去了。所以當(dāng)我們寫(xiě)box.Point.x=5的時(shí)候,其實(shí)等價(jià)于box.get_Point().x=5。你拿到的Point是復(fù)制過(guò)的,你對(duì)一個(gè)復(fù)制過(guò)的struct來(lái)修改里面的x,自然不能影響box里面存放著的那個(gè)Point。所以這是一個(gè)無(wú)效語(yǔ)句,C#干脆就給你定了個(gè)編譯錯(cuò)誤了。不過(guò)你可能會(huì)問(wèn),List和Array大家都是operator[]也是一個(gè)屬性,那為什么Array就可以呢?答案很簡(jiǎn)單,Array是有特殊照顧的……
不過(guò)話說(shuō)回來(lái),為什么很少人遇到這個(gè)問(wèn)題?想必是能寫(xiě)成struct的這些東西,作為整體來(lái)講本身是一個(gè)狀態(tài)。譬如說(shuō)上面的Point,x和y雖然是分離的,但是他們并不獨(dú)立代表狀態(tài),代表狀態(tài)的是Point這個(gè)整體。Tuple(這是個(gè)class,不過(guò)其實(shí)很像struct)也一樣,還有很多其他的.net framework里面定義的struct也一樣。因此就算我們經(jīng)常構(gòu)造List<Point>這種東西,我們也很少要去單獨(dú)修改其中一個(gè)element的一部分。
那為什么struct不干脆把每一個(gè)field都做成不可修改的呢?原因是這樣做完全沒(méi)有帶來(lái)什么好處,反正你誤操作了,總是會(huì)有編譯錯(cuò)誤的。還有些人可能會(huì)問(wèn),為什么在struct里面的方法里,對(duì)this的操作就會(huì)產(chǎn)生影響呢?這個(gè)問(wèn)題問(wèn)得太好了,因?yàn)閠his是一個(gè)本質(zhì)上是“指針”的東西。
這就跟上一篇文章所講的東西不一樣了。這篇文章的兩個(gè)“坑”其實(shí)不能算坑,因?yàn)樗麄冏罱K都會(huì)引發(fā)編譯錯(cuò)誤來(lái)迫使你必須修改代碼。所以說(shuō),如果C++的new T[x]返回的東西是一個(gè)貨真價(jià)實(shí)的數(shù)組,那該多好啊。數(shù)組質(zhì)檢科從來(lái)沒(méi)有什么轉(zhuǎn)換的。就像Delphi的array of T也好,C#的T[]也好,C++的array<T>或者vector<T>也好,你從來(lái)都不能把一個(gè)T的數(shù)組轉(zhuǎn)成U的數(shù)組,所以也就沒(méi)有這個(gè)問(wèn)題了。所以在用C++的時(shí)候,STL有的東西,你就不要自己擼了,只傷身體沒(méi)好處的……
那么回到一開(kāi)始說(shuō)的const的問(wèn)題。我們?cè)贑++里面用const,一般都是有兩個(gè)目的。第一個(gè)是用const引用來(lái)組織C++復(fù)制太多東西,第二個(gè)是用const指針來(lái)代表某些值是不打算讓你碰的。但是一個(gè)類(lèi)里面的函數(shù)會(huì)做什么我們并不知道,所以C++給函數(shù)也加上了const。這樣對(duì)于一個(gè)const T的類(lèi)型,你只能調(diào)用T里面所有標(biāo)記了const的函數(shù)了。而且對(duì)于標(biāo)記了const的成員函數(shù),他的this指針也是const T* const類(lèi)型的,而不是以前的T* const類(lèi)型。
那類(lèi)似的問(wèn)題在C#里面是怎么解決的呢?首先第一個(gè)問(wèn)題是不存在的,因?yàn)镃#復(fù)制東西都是按bit復(fù)制的,你的struct無(wú)論怎么寫(xiě)都一樣。其次,C#沒(méi)有const類(lèi)型,所以如果你想表達(dá)一個(gè)類(lèi)不想讓別人修改,那你就得把那些“const”的部分抽出來(lái)放在父類(lèi)或父接口里面了。所以現(xiàn)在C#里面除了IList<T>類(lèi)型以外,還有IReadOnlyList<T>。其實(shí)我個(gè)人覺(jué)得IReadOnlyList這個(gè)名字不好,因?yàn)檫@個(gè)對(duì)象說(shuō)不定底下是個(gè)List,你用著用著,因?yàn)閯e人改了這個(gè)List導(dǎo)致你IReadOnlyList讀出來(lái)的東西變了,迷惑性就產(chǎn)生了。所以在這種情況下,我寧可叫他IReadableList。他是Readable的,只是把write的接口藏起來(lái)的你碰不到而已。
所以,const究竟是在修飾什么的呢?如果是修飾類(lèi)型的話,跟下面一樣讓函數(shù)的參數(shù)的類(lèi)型都變成const,似乎完全是沒(méi)有意義的:
int Add(const int a, const int b);
或者更甚,把返回值也改成const:
const int Add(const int a, const int b);
那他跟
究竟有什么區(qū)別呢?或許在函數(shù)內(nèi)部你不能把參數(shù)a和b當(dāng)變量用了。但是在函數(shù)的外部,其實(shí)這三個(gè)函數(shù)調(diào)用起來(lái)都沒(méi)有任何區(qū)別。而且根據(jù)我們的使用習(xí)慣來(lái)講,const修飾的應(yīng)該不是一個(gè)類(lèi)型,而是一個(gè)變量才對(duì)。我們不希望IBitmap::Apply函數(shù)里面會(huì)修改filter,所以函數(shù)簽名就改成了:
virtual void Apply(const IMatrix<float>& filter)=0;
我們不希望用宏來(lái)定義常數(shù),所以我們會(huì)在頭文件里面這么寫(xiě):
const int ADD = 1;
const int SUB = 2;
const int MUL = 3;
const int DIV = 4;
const int PUSH = 5;
const int POP = 6;
或者干脆用enum:
enum class Instructions
{
ADD = 1,
SUB,
MUL,
DIV,
PUSH,
POP
};
對(duì)于C++來(lái)講,const還會(huì)對(duì)鏈接造成影響。整數(shù)數(shù)值類(lèi)型的static const成員變量也好,const全局變量也好,都可以只寫(xiě)在頭文件給一個(gè)符號(hào),而不需要在cpp里面定義它的實(shí)體。但是對(duì)于非static const的成員變量來(lái)說(shuō),他又占用了class的一些位置(C#的const成員變量跟static是不相容的,它只是一個(gè)符號(hào),跟C++完全不是一回事)。
而且根據(jù)大部分人對(duì)const的認(rèn)識(shí),我們用const&也好,const*也好,都是為了修飾一個(gè)變量或者參數(shù)。譬如說(shuō)一個(gè)臨時(shí)的字符串:
const wchar_t* name = L"@GeniusVczh";
或者一個(gè)用來(lái)計(jì)算16進(jìn)制編碼的數(shù)組:
const wchar_t code[] = L"0123456789ABCDEF";
其實(shí)說(shuō)到底,我們心目中的const都是為了修飾變量或者參數(shù)而產(chǎn)生的,說(shuō)白了就是為了控制一個(gè)內(nèi)存中的值是否可以被更改(這一點(diǎn)跟volatile一樣,而C#的volatile還帶fence語(yǔ)義,這一點(diǎn)做得比C++那個(gè)只用來(lái)控制是否可以被cache進(jìn)寄存器的要強(qiáng)多了)。所以C++用const來(lái)修飾類(lèi)型又是一個(gè)違反直覺(jué)的設(shè)計(jì)了。當(dāng)然,如果去看《C++設(shè)計(jì)與演化》的話,的確可以從中找到一些講為什么const會(huì)用來(lái)描述類(lèi)型的原因。不過(guò)從我的使用經(jīng)驗(yàn)上來(lái)看,const至少給我們帶來(lái)了一些不方便的地方。
第一個(gè)就是讓我們寫(xiě)一個(gè)正確的C++ class變得更難。就像C#里面說(shuō)的,一個(gè)只讀的列表,其實(shí)跟一個(gè)可讀寫(xiě)的列表的概念是不一樣的。在C++里面,一個(gè)只讀的列表,是一個(gè)可以讓你看見(jiàn)寫(xiě)函數(shù)卻不讓你用的一個(gè)進(jìn)入了特殊狀態(tài)的可讀寫(xiě)的列表。一般來(lái)說(shuō),一個(gè)軟件都要幾千個(gè)人一起做。我今天寫(xiě)了一個(gè)類(lèi),你明天寫(xiě)了一個(gè)帶const T&參數(shù)的模板函數(shù),后天他發(fā)現(xiàn)這兩個(gè)東西湊在一起剛好能用,但是一編譯發(fā)現(xiàn)那個(gè)類(lèi)的所有成員函數(shù)都不帶const結(jié)果沒(méi)辦法搞了。怎么辦?重寫(xiě)嗎,那我們得自己維護(hù)多出來(lái)的一份代碼,還可能跟原類(lèi)的作者犯下一樣的錯(cuò)誤。修改它的代碼嗎,鬼知道給一個(gè)函數(shù)加上const會(huì)不會(huì)給這個(gè)超大的軟件的其他部分帶來(lái)問(wèn)題,說(shuō)不定就像字符串類(lèi)一樣,有一些語(yǔ)義上是const的函數(shù)實(shí)際上需要修改一些成員變量結(jié)果你又不得不給那些東西加上mutable關(guān)鍵字了。你修改了之后,代碼誰(shuí)來(lái)維護(hù),又成為一個(gè)跟技術(shù)無(wú)關(guān)的政治問(wèn)題了。而且就算你弄明白了什么函數(shù)要加const,結(jié)果你聲明一個(gè)const變量的時(shí)候const放錯(cuò)了位置,也會(huì)有一些莫名其妙的問(wèn)題出現(xiàn)了。
如果從一開(kāi)始就用C#的做法,把它分離成兩個(gè)接口,這樣做又跟C++有點(diǎn)格格不入,為什么呢?為什么STL那么喜歡泛型+值類(lèi)型而不是泛型+引用類(lèi)型?為什么C#就喜歡泛型+引用類(lèi)型而不是泛型+值類(lèi)型?其實(shí)這兩種設(shè)計(jì)并沒(méi)有誰(shuí)好誰(shuí)不好的地方,至于C++和C#有不同的偏愛(ài),我想原因應(yīng)該是出在GC上。語(yǔ)言有GC,你new的時(shí)候就不需要擔(dān)心什么時(shí)候去delete,反正內(nèi)存可以循環(huán)回收總是用不完的。C++卻不行,內(nèi)存一旦leak就永遠(yuǎn)的leak了,這么下去遲早都會(huì)掛掉的。所以當(dāng)我們?cè)贑++和C#里面輸入new這個(gè)關(guān)鍵字的時(shí)候,心情其實(shí)是差別相當(dāng)大的。所以大家在C++里面就不喜歡用指針,而在C#里面就new的很開(kāi)心。既然C++不喜歡指針,類(lèi)似IReadOnlyList<T>的東西不拿指針直接拿來(lái)做值類(lèi)型的話又是沒(méi)有什么意義的,所以干脆就加上了const來(lái)“禁止你訪問(wèn)類(lèi)里面的一部分東西”。于是每當(dāng)你寫(xiě)一個(gè)類(lèi)的時(shí)候,你就需要思考上一段所描述的那些問(wèn)題。但是并不是所有C++的程序員都知道所有的這些細(xì)節(jié)的,所以后面加起來(lái),總會(huì)有傻逼的時(shí)候——當(dāng)然這并不怪C++,怪的是你面試提出的太容易,讓一些不合格的程序員溜進(jìn)來(lái)了。C++不是誰(shuí)都可以用的。
第二個(gè)問(wèn)題就是,雖然我們喜歡在參數(shù)上用const T&來(lái)避免無(wú)謂的復(fù)制,但是到底在函數(shù)的返回值上這么做對(duì)不對(duì)呢?const在返回值的這個(gè)問(wèn)題上這是一把雙刃劍。我自己寫(xiě)過(guò)一個(gè)linq for C++,山寨了一把IEnumerable和IEnumerator類(lèi),在Current函數(shù)里面我返回的就是一個(gè)const T&。本來(lái)容器自己的IEnumerator寫(xiě)的挺好,因?yàn)楸緛?lái)返回的東西就在容器里面,是有地址的。但是開(kāi)始寫(xiě)Select和Where的時(shí)候就傻逼了。我為了正確返回一個(gè)const T&,我就得返回一個(gè)帶內(nèi)存地址的東西,當(dāng)然最終我選擇了在MoveNext的時(shí)候把結(jié)果cache在了這個(gè)SelectEnumerator的成員變量里面。當(dāng)然這樣做是有好處的,因?yàn)樗麖?qiáng)迫我把所有計(jì)算都放在MoveNext里面,而不會(huì)偷懶寫(xiě)在Current里。但是總的來(lái)說(shuō),要不是我寫(xiě)代碼的時(shí)候蛋定,說(shuō)不定什么時(shí)候就掉坑里了。
總的來(lái)說(shuō),引入const讓我們寫(xiě)出一個(gè)正確的C++程序的難度變大了。const并不是一無(wú)是處,如果你是在想不明白什么時(shí)候要const什么時(shí)候不要,那你大不了不要在自己的程序里面用const就好了。當(dāng)然我在這里并不是說(shuō)C語(yǔ)言什么都沒(méi)有就比C++好。一個(gè)語(yǔ)言是不可能通過(guò)刪掉什么來(lái)讓他變得更好的。C語(yǔ)言的抽象能力實(shí)在是太低了,以至于讓我根本沒(méi)辦法安心做好邏輯部分的工作,而總要關(guān)心這些概念究竟要用什么樣的扭曲的方法才能在C語(yǔ)言里面比較順眼的表達(dá)出來(lái)(我知道你們最后都選擇了宏!是吧!是吧!),從而讓我變“煩”,bug就變多,程序到最后也懶得寫(xiě)好了,最后變成了一坨屎。
嘛,當(dāng)然如果你們說(shuō)我沒(méi)有l(wèi)inus牛逼,那我自然也沒(méi)辦法說(shuō)什么。但是C語(yǔ)言大概就是那種只有l(wèi)inus才能用的順手的語(yǔ)言了。C++至少如果你心態(tài)好的話,沒(méi)事多用STL,掉坑的概率就要比直接上C語(yǔ)言小多了。
語(yǔ)言的坑這種事情實(shí)在是罄竹難書(shū)啊,本來(lái)以為兩篇文章就可以寫(xiě)完的,結(jié)果發(fā)現(xiàn)遠(yuǎn)遠(yuǎn)不夠。看在文章長(zhǎng)度的份上,今天就到此為止了,下一篇文章還有大家喜聞樂(lè)見(jiàn)的函數(shù)指針和lambda的大坑等著你們……
待續(xù)
posted on 2013-04-28 02:26
陳梓瀚(vczh) 閱讀(14717)
評(píng)論(17) 編輯 收藏 引用 所屬分類(lèi):
啟示