• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆-341  評(píng)論-2670  文章-0  trackbacks-0

            我從來(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);

             

            那他跟

            int Add(int a, 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): 啟示

            評(píng)論:
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b) 2013-04-28 03:17 | 甜品專(zhuān)家
            LZ好人,一生平安。  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b)[未登錄](méi) 2013-04-28 05:52 | diryboy
            .Net: Immutable collection is watching at you.  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b) 2013-04-28 06:45 | C19
            C++不是誰(shuí)都可以用的。....
            想起一句話:C是人類(lèi)的語(yǔ)言。C++是妖怪的語(yǔ)言。。。  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b) 2013-04-28 07:32 | DKWings
            文末“第一個(gè)就是讓我們寫(xiě)一個(gè)正確的C++ class變得更難。……”一段真是深得我心,看的時(shí)候忍不住擊桌叫好。第一次看到可以typedef一個(gè)const類(lèi)型讓我覺(jué)得有不舒服之感,而“我們心目中的const都是為了修飾變量或者參數(shù)而產(chǎn)生的”一句真正歸納了const的用法,讓我頓感清晰。其后接的一段更是讓我有覺(jué)得有高屋建瓴之感。然而,沒(méi)有const的類(lèi)和函數(shù)往往讓我感到不安全;其實(shí)在有經(jīng)驗(yàn)的情況下,95%的情況下還是能寫(xiě)出易用的const類(lèi)的——但我如何保證別人也有如此經(jīng)驗(yàn)?zāi)兀繃@氣。  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b) 2013-04-28 07:38 | hugh
            我在想,如果cpp沒(méi)有const的話,右值就不會(huì)綁定在const&上了。右值引用估計(jì)n久前就出來(lái)了,用不著等到cpp11了。  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b) 2013-04-28 09:02 | 溪流
            學(xué)習(xí)了  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b) 2013-04-28 20:11 | 老趙
            其實(shí)在.NET命名規(guī)范里面,ReadOnlyXxx是沒(méi)有Immutable和Thread Safe等語(yǔ)義的,包括ReadOnlyCollection/List/Dictionary等等,同樣的還有IEnumerable之類(lèi)的……假如真要“不可變”,只能ImmutableXxx啥啥了。  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b)[未登錄](méi) 2013-04-28 20:55 | ken
            原來(lái)C#的Array還是特殊照顧才行啊。。。
            開(kāi)始沒(méi)看懂放C++語(yǔ)法的話其實(shí)就是返回T和返回T&的區(qū)別吧?
            值類(lèi)型不能直接賦值還是覺(jué)得怪怪的  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b) 2013-04-28 21:39 | 老趙
            @ken
            怎么說(shuō)呢,我覺(jué)得也不好說(shuō)是特殊照顧,而是它本來(lái)這么做是合適的……
            因?yàn)橹殿?lèi)型在傳遞過(guò)程中會(huì)復(fù)制出來(lái)了一份(或N份,好吧),假如不是Field或是Array這種直接就訪問(wèn)到那個(gè)對(duì)象里的那個(gè)值的情況,你++或賦值了等于什么都沒(méi)做,像Property或是List的accessor什么的其實(shí)就是一種特殊的方法,肯定會(huì)復(fù)制出一份,所以編譯器就不讓你做了。
            當(dāng)然你也可以硬說(shuō)Array的Index Accessor是一種特殊照顧,哦吼吼……  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b) 2013-04-28 21:52 | 陳梓瀚(vczh)
            @老趙
            難道不是嗎,array自己可以this[int index]卻不行,啊哈哈哈哈  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b) 2013-04-28 22:41 | 老趙
            @陳梓瀚(vczh)
            也可以算特殊照顧吧,但array這東西因?yàn)橐回灳褪侵苯佑眠@種[]語(yǔ)法訪問(wèn)元素的,訪問(wèn)的也直接是那個(gè)元素,而this[this index]是學(xué)array的,但最后是變成了方法的東西,所以會(huì)產(chǎn)生復(fù)制。
            當(dāng)然假如把這種[]語(yǔ)法看作是一類(lèi)東西,那么的確array的跟其他的List之類(lèi)的是不同的,說(shuō)它是一種特殊對(duì)待也不是不行,總之不管是[]還是Field,就看是直接訪問(wèn)還是通過(guò)方法來(lái)“間接”一下了……  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b)[未登錄](méi) 2013-05-06 19:30 | kkk
            struct作為屬性修改field的問(wèn)題其實(shí)用WPF/SL的人很早也會(huì)很頻繁就遇到,因?yàn)榭丶腗argin屬性是不能直接control.Margin.Left = xxx這樣的,每次都必須new  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b) 2013-05-09 01:36 | xiaodong
            我最羨慕你的就是可以在MS這一直折騰  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b) 2013-05-25 23:07 | Marvin
            我覺(jué)得造成噴子的原因是
            1. 本來(lái)語(yǔ)言就不完美,目前別說(shuō)實(shí)現(xiàn),就是設(shè)計(jì)完美的語(yǔ)言也不存在。所以語(yǔ)言本身就是缺陷,這也是有的噴。
            2. 噴子大多只懂自己熟悉的語(yǔ)言,沒(méi)有站在技術(shù)或科技的角度想語(yǔ)言的問(wèn)題。  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b) 2013-08-16 00:50 | 幻の上帝
            喜歡不喜歡指針對(duì)C++的“類(lèi)似IReadOnlyList<T>的東西不拿指針直接拿來(lái)做值類(lèi)型的話又是沒(méi)有什么意義的”是次要的問(wèn)題。要命在于直接用值的話都沒(méi)法子類(lèi)多態(tài)……
            至于const嘛,我是不覺(jué)得修飾單個(gè)值/對(duì)象有什么好的。本來(lái)就已經(jīng)跟lvalueness重得過(guò)頭了,不限定類(lèi)型不是更浪費(fèi)。修飾類(lèi)型大概也可以指望別人會(huì)多去了解const-correctness,或者至少會(huì)讓別人少個(gè)借口擼出IReadOnlyList這種不科學(xué)的命名來(lái)。
            C++的const問(wèn)題說(shuō)起來(lái)是挺麻煩的——不只是指特性,也包括語(yǔ)言自身的演化。主要表現(xiàn)在:
            1.想要做的事情太多卻沒(méi)做好——導(dǎo)致const-expression不倫不類(lèi)。一部分事情現(xiàn)在攤給constexpr了,不過(guò)舊包袱沒(méi)那么容易甩掉。(cl什么時(shí)候支持啊……)
            2.更大的歷史包袱:和lvalueness/value category的目的很大部分重復(fù),卻也不能徹底取代。而且后者越搞越復(fù)雜,更不用指望了。
            3.其它副作用。像重載規(guī)則啊qualification conversion啊什么的明顯搞復(fù)雜了……
            說(shuō)回來(lái)關(guān)于const放哪應(yīng)該是基礎(chǔ)問(wèn)題吧。
            const int做參數(shù)不會(huì)多一個(gè)overloadable function出來(lái)也應(yīng)該是常識(shí)吧。
            至于const放返回值……不知道有多少童鞋不清楚C/C++里non-lvalue是沒(méi)有cv-qualifier修飾的呢……sigh.


              回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b) 2013-08-16 00:53 | 幻の上帝
            ……抽了,class type lvalue還是可以有cv-qualifier的。(不過(guò)一般放返回值里也沒(méi)什么意思。)  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(二)&mdash;&mdash;什么是坑(b) 2014-02-19 08:46 | natsu
            If T is a non-class type, the type of the rvalue is the cv-unqualified version of T.
            In C++ class rvalues can have cv-qualified types (because they are objects). This differs from ISO C, in which non-lvalues never have cv-qualified types.  回復(fù)  更多評(píng)論
              
            99久久人妻无码精品系列| 久久国产精品-久久精品| 成人国内精品久久久久一区| 囯产极品美女高潮无套久久久| 久久久艹| 欧美日韩中文字幕久久久不卡| 国产精品亚洲美女久久久| 99久久综合狠狠综合久久| 久久久久综合网久久| 久久精品国产影库免费看| 亚洲成色999久久网站| 日韩欧美亚洲综合久久影院d3| 国产成人精品白浆久久69| 久久精品www人人爽人人| 国产∨亚洲V天堂无码久久久 | 国产精品亚洲综合专区片高清久久久 | 99精品久久精品一区二区| 72种姿势欧美久久久久大黄蕉| 久久99国内精品自在现线| 狠狠色丁香久久婷婷综合五月| 99久久99久久| 久久人人超碰精品CAOPOREN| 人人狠狠综合88综合久久| 久久这里都是精品| 国产成年无码久久久免费| 久久亚洲精品国产精品| 91麻豆精品国产91久久久久久| 久久精品国产一区二区三区不卡| 久久人人爽人人爽AV片| 亚洲国产另类久久久精品黑人 | 94久久国产乱子伦精品免费 | 久久久无码精品亚洲日韩按摩 | 久久这里只精品国产99热| 国内精品久久久久久久影视麻豆| 国产精品一区二区久久精品涩爱| 久久丫精品国产亚洲av不卡 | 久久亚洲AV无码精品色午夜 | 国内精品久久久久久久久| 久久国内免费视频| 91精品国产91久久久久福利| 精品无码人妻久久久久久|