• <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>

            huaxiazhihuo

             

            stl中string的一種改造

                  stl中最難看的組件(沒有之一),無疑就是string這貨了,一百多個成員函數,當然里面大多數是重載的,不必多想,一個class,如果擁有如此之多的函數,必然一定肯定是失敗的,并且,即便是這么一大打函數,string的功能還是很不完備,要不然,就不會有boost里面的string算法。這真是尷尬,string作為最基本最基本的語言組件,又出自官方標準庫,長成這樣子,真是讓無數的c++粉絲要失望,失望歸失望,畢竟師出iso,用起來還是很有保障的,論性能什么,再怎樣,也不會虧到那里去。只是,很讓人好奇的是,這成百個函數又功能不完備的string,里面都有些什么貨色,對此,c++exception系列中有過分析。但是,在此,想探討一下,除了小胡子的方法之外,用其他方法壓縮string的成員函數的數量。
                  我們先來看看string的append成員函數,怪怪龍的東,總共有8個重載之多,好像還不止,突然想起狗語言的名言,少即是多,反過來說,多即是少。
            basic_string<CharType, Traits, Allocator>& append(
                 
            const value_type* _Ptr
            );
            basic_string
            <CharType, Traits, Allocator>& append(
                 
            const value_type* _Ptr,
                 size_type _Count
            );
            basic_string
            <CharType, Traits, Allocator>& append(
                 
            const basic_string<CharType, Traits, Allocator>& _Str,
                 size_type _Off,
                  size_type _Count
            );
            basic_string
            <CharType, Traits, Allocator>& append(
                 
            const basic_string<CharType, Traits, Allocator>& _Str
            );
            basic_string
            <CharType, Traits, Allocator>& append(
                 size_type _Count, 
                 value_type _Ch
            );
            template
            <class InputIterator>
                 basic_string
            <CharType, Traits, Allocator>& append(
                     InputIterator _First, 
                       InputIterator _Last
                  );
            basic_string
            <CharType, Traits, Allocator>& append(
                    const_pointer _First,
                   const_pointer _Last
            );
            basic_string
            <CharType, Traits, Allocator>& append(
                    const_iterator _First,
                   const_iterator _Last
            );
                  這么多的重載,其實可分為兩類,一類是迭代器版本的append,對于插入n個相同的字符append,可以看做是特殊迭代器。另一類是連續字節內存塊的append。這里,只關注后一類。雖然有4個之多,但其實只需要一個就行了,那就是 append(const basic_string<CharType, Traits, Allocator>& _Str)。因為字符指針可以隱式轉換為string,另外的兩個重載可以臨時構造string,然后傳遞進append就好了。之所以存在4個,老朽的猜想可能是因為效率,至于調用上的方便性,并沒有帶來多少提高。string的其他類似于用append的通過參數來string的操作,如replace,insert,+=,那么多的重載版本,應該也是同樣的原因。
                  假如,臨時string對象的構造沒有造成任何性能上的損失,那么,應該就可以減少幾十個成員函數,這無疑很值得嘗試。那么,能否存在廉價的string臨時構造方法,因為它知道自己是臨時對象,只作為臨時參數傳遞的使命,不會在其上面作什么賦值,添加,修改等操作,也就是說,它是不可變的,那么,這個臨時string對象就不需要分配內存了,只要節用ptr作為自己字符串的起始地址,然后以長度作為自己的長度。參數傳遞使命完成后,也不需要銷毀內存了。
                  可是,C++中,也不僅僅是C++,所有的語言并沒有這樣的機制來判斷對象它在構造的時候,就是僅僅作為參數傳遞來使用的。為了達到這種目的,很多時候還不惜使用引用計數,但是,很多場合,臨時string對象始終要構造緩沖存放字符串,比如這里。
            除了C++,任何語言的字符串都是不可變的,任何對于字符串的修改,都意味著要創建另一個全新的字符串來,那怕僅僅是修改了一個字符。其實,不可變的字符串,在C++中運用很廣的,很多時候,我們僅僅只需要不可變的字符串,比如說,這里的append,全部只需要immutable的string。只要知道string是immutable的,那么,c++完全可以高效的應付,既然是immutable,就不需要考慮什么資源分配釋放的龜毛問題了。下面,就嘗試class一個immutable的字符串,這,太容易了。就是:
            struct Str
            {
                typedef 
            const char* PCStr;
                PCStr start;
                size_t length;
                Str(PCStr text, size_t len)
                {
                    start 
            = text;
                    length 
            = len;
                }
                
            //
            };
                  然后,在basic_string中加入operator Str的函數,以完成從一個string到一個Str的隱式轉換,這個隱式轉換簡直沒有任何性能上的損失。還有,string中再增加一個Sub的成員函數,用于截取一段子字符串,也即是immutable的Str對象。顯然,我們的Str其實表達了一個概念,內存中一節連續的字符內存,也即是數組。
                  最后,append就變成append(Str str);了。Str加不加const,或者Str是否為引用,關系都不大。下面,看看它的運作。
            對于,append(const char* text),由于Str中有一個const char*參數的構造函數,text自動隱式轉換為一個Str,很好;
            對于,append(const char* text,size_t count),用append(Str(text, count)),就地構造一個臨時的Str對象,嗯,語法調用上多了一個Str和一對括號,多了5個字符,的確有點不便。
            對于,append(const string& text),同上,string中有一個operator Str的函數,隱式轉換自動完成。
            對于,append(const string& text,size_t offset,size_t count),用append(text.Sub(offse, count)),就地構造一個臨時的Str對象,嗯,語法調用上多了一個Sub和一對括號和一個點,但是少了一個逗號,多了5個字符,有點不便。
                  即此以推,string中的replace,insert,assign,+=,=等函數,每個平均減少3個,總共差不多可以減少20個左右啦,而功能上沒有任何減少,可喜可賀。
                  然后,string中的各種查找比較操作的const的成員函數,比如find,find_first_not_of,rfind等,都可以挪到Str旗下了。因為這些函數,我們也希望可以用之于其他地方,只要那是一塊連續的字符內存好比數組,那么我們就可以就地快速構造一個臨時Str對象,進行find,rfind這些操作了。當然,原來string也可以有這個功能,但是想到僅僅為了做一個find或者find_first_not_of的查找,就要分配內存釋放內存,對于性能優先的巴普洛夫反應的C++猿猴來說,這絕對是望而生畏的大事。現在通過不可變的Str,馬上就釋放出來string的成員函數的隱含的生產力了。 由于Str的廉價和透明性,就可以到處亂使用,想用就用,何其快哉。
                  原來string沒有了這些查找的函數,每次要用它們,必須轉換這樣調用,((Str)text).find,無疑很不方便,對此,我們只要在string中再增加一個Str的成員函數,以返回臨時Str對象,就可以text.Str().find(),似乎有點不便,但也不是不能接受。
            當然,Str也有缺點,那就是它不以0結束,導致很多對于要求以0結束的地方,就變成禁區了,這坑爹的C語言規定。
                  這不是很明顯嗎?字符串的一部分也是字符串,隨便取出字符串的一節,本來就應該是字符串,這么簡明統一簡潔明顯的概念,這樣可以簡化多少代碼呢,結果,偏偏只有帶有0結束的那一節字符串,才是C語言承認的字符串。一個很好的概念,就這樣在很多地方失去用武之地了。你因為以0結束的字符串很好嗎,要不cstring頭文件中也不會有那么多帶有字符串長度版本的字符函數,如strncpy,來補充了。
                  對了,有沒有覺得string中的find_last_of,find_first_of,find_last_not_of,find_first_not_of很礙眼啊,顯然這是一種不用組合思想下設計出來的api產物了。其實,別看stl是官方iso的嫡出親子,但是,內中的很多api的設計都不咋樣,實在不是學習的好對象。你還別不服,想想人家C#linq的鏈式調用,那個用起來,才叫痛快。

            posted @ 2016-05-09 19:28 華夏之火 閱讀(1400) | 評論 (2)編輯 收藏

            scheme下的停機問題和Y組合子

                    看過的計算機書中,scheme相關的那幾本,好比SICP,the essence of program都很讓我愛不釋手。而the little schemer更加獨特,編程的本質,在這本書小人書上體現得淋漓盡致。竊以為,scheme是語法形式上最為完美的編程語言了,沒有之一。少即是多,這樣的贊美之言,唯有scheme當之無愧,并且它的確是精簡得不能再精簡了。至于那個自吹自擂的什么狗語言,不提也罷。當然,完美并不一定代表實用,也并不一定必須流行,曲高一向都是和寡的,但是,完美卻一定可以帶來賞心悅目般的感受。
                    the little schemer全書行云流水,逐漸顯露遞歸的威力,做足了鋪墊,到了第8章,真命天子lambda出現,一切變得很有意思了,讀完之后,意猶未盡。第9章,難度陡增,突然變得理論性起來,那是自然的。因為,這一章的主題是停機問題和Y組合算子。不引入任何形式化的方法,但是作者舉重若輕,依然闡釋得如此直白易懂,可以說,只要能看完前面的內容,就一定能看懂這一章。而前八章,據說6歲以上的兒童都能看得明白。
                    scheme中的函數是first class,可以作為參數傳遞給其他函數,也可以作為值從函數中返回。比如,廣為流傳的一道程序,用以考察語言的表達能力,“編寫一個函數,其入參數為n,返回值為新的函數,該函數的參數為x,返回值為之前的n與現在的x的和。”用scheme來表達,牛刀小試。
            (define (addn n)
              (lambda (x)
                (+ x n)))
            然后,對于((addn 20) 30),scheme解釋器上顯示其結果為50,很好。相比于那個lisp的版本,這里顯得多么的干凈。
                    函數式的語言對副作用(side effect)很敏感,特別是haskell,更加對副作用趕盡殺絕,壓根就不讓寫出有副作用的函數。因此,正常情況下,函數執行完畢,都有返回值。好比,……,總之很多就是,正常的函數,都可稱之為total functions,意思就是對所有的參數,都會有返回結果。但是,也還存在一些病態函數,它們不會返回,一旦調用它,那么將陷入與其中,永遠都不會返回了,顯然,里面出現死循環了,但是,scheme中沒有循環語句,所以不能這么說,總之,這一類是不會有返回值的。很輕易就能寫出一個例子。
            (define eternity
              (lambda (x)
                (eternity x)))
            eternity為不朽的意思。
                    自然就有這樣的問題,能否實現這樣的函數,它能判斷函數是否終將返回,或者說,判斷函數會不會停止。這個函數作用可大了,當然不會那么容易實現。不過,可以先假設它存在,就叫它will-stop?(別驚訝,scheme中,標識符中可以有+-*等特殊符號)。因此,對于任何函數,(will-stop? afunction)表達式的值,要么為#t,表示函數afunction終將停止返回;要么為#f,則函數不會停止,好比eternity。顯然,讓will-stop?判斷自己,(will-stop? will-stop?)的結果一定是#t了。
                    但是,will-stop?是不可能存在的,這不是廢話嗎,地球人都知道。因為計算機學家精心構造了一個反例,此反例實在巧妙,真難以想象當初是如何構造出來,我等小民只需理解即可。請看代碼
            (define (last-try x)
              (and (will-stop? last-try) (eternity x)))
            last-try,好名字,就叫它最后一擊吧。(will-stop? last-try)的結果不外乎#t或#f。
                    假如為#f,說明last-try不會返回,意味著有死循環,不會停止。但是,一考察last-try的內部實現,卻很容易就知道它馬上就返回了。表達式(and (will-stop? last-try) (eternity '()))中,由假設可知(will-stop? last-try)為#f,進而馬上可知,(and (will-stop? last-try) (eternity '()))馬上必將返回#f,也就是說,雖然一開始假設last-try不會停止,但實際運行中last-try一下子就返回了,矛盾。
                    看樣子,(will-stop? last-try)只好為#t了。可是,(and (will-stop? last-try) (eternity '())),and表達式的兩個分支中,既然(will-stop? last-try)為#t,那么,勢必要進一步調用(eternity '()),而eternity老爺,一早就知道他乃不朽之身了,因此,last-try也沾光,一樣不朽了。與假設中(will-stop? last-try)為#t為終將停止,又是矛盾。
                    因此,will-stop?接受不了last-try的挑戰,失敗。也就是說,will-stop?這樣的函數,不存在。這道反例的高明之處,或者說耍賴吧,就是以will-stop?為基礎構造了一個will-stop?無法判斷的函數。假如規定,所有被檢測函數都不得直接間接的調用will-stop?,免得will-stop?難堪,那么這樣的will-stop?能否存在呢?存不存在,我就不知道了,但享受此待遇的Y組合子卻是存在的。
                    函數直接或間接調用到它自己,遞歸就產生了。問題來了,函數你自己都還沒實現完畢,怎么就可以自己拿來調用呢?這個過程中,編譯器解釋器肯定做了某些語義上處理,讓遞歸得以實現。邏輯學中,對于下定義的要求是“不得循環”,好比,白色就是一種白色的顏色,這種廢話定義就不符合下定義的基本要求了。
                    下面來將一條經典的遞歸函數整成非遞歸的版本。the little schemer的推導思路非常淺顯易懂,我不能做的更好的了,因此借用。
            (define length
              (lambda (l)
                (cond ((null? l) 0)
                  (else (+ 1 (length (cdr l)))))))
            函數length中,雖然調用到了自己,實際上,其實只是調用了一個同樣名字的函數而已。意味著,length的實際上的lambda表達式,背地里帶多了一個參數,此參數為函數,用以當入參l不為空時來進行使用。因此,可以將整個函數的定義改寫成下面的lambda表達式。
            (lambda (length)
              (lambda (l)
                (cond ((null? l) 0)
                  (else (+ 1 (length (cdr l)))))))
            lambda表達式的返回值為一個函數,當然沒有名字了。它的入參為一函數,返回一個新的函數,此新函數的入參是列表,返回列表的長度。為了便于后文敘述引用,就用define給它起個名字,叫mk-length。什么,連用define起名字都不會,沒救了。
                    mk-length不是需要函數入參嗎?剛好手頭有一個,就用它自己本身,((mk-length mk-length) '()),解釋器返回0,太好了。然后,我滿懷希望的用((mk-length mk-length) '(a))來測試,結果,解釋器報錯了,為什么?稍微一想,就明白了。(mk-length mk-length)的確返回計算列表長度的函數,但是,當列表不為空時,只好用表達式(+ 1 (length (cdr l)))做進一步處理,里面的length就是mk-length,而mk-length的入參是函數,不是列表,于是解釋器就報錯了。怎么辦?
                    當然,要計算長度為不大于1的列表的長度,還是有辦法的。就是,((mk-length (mk-length mk-length)) '(a)),這樣就好了。自然,當列表大于1時,解釋器必然又將報錯了。按照此法,為此,為了求得不大于N個元素的列表長度,必須將mk-length寫N次,好比,
            ((mk-length
              (mk-length
               (mk-length (...))))
             '(a b c d ...))
            并且,辛辛苦苦的重復寫N遍mk-length,只能計算個數不大于N的列表的長度。這,無論如何都不能讓程序猿接受。
            那么,為何要寫那么多(mk-length (mk-length (mk-length...))),皆因mk-length中(+ 1 (length (cdr l)))的length函數接收的函數參數是列表l。先暫時讓它適應環境,就讓它知道它接收的length參數是一個跟它自己本身的lambda表達一樣,是入參為函數,然后返回一個計算list長度的函數。將mk-length改寫成這樣。
            (define mk-length
              (lambda (length)
                (lambda (l)
                  (cond ((null? l) 0)
                    (else (+ 1 ((length length) (cdr l))))))))
            請注意,代碼里面已經不存在遞歸形式了,因為,mk-length的lambda表達式中,沒有用到mk-length這個名字了,當然,它還要用到入參length以計算當l不為空時的長度。再次抱著試試看的態度,驗證,((mk-length mk-length) '(a)),返回1,真的可以了。拿更長的列表丟進去,長度為2,為3,為N+1,都OK了,真是神奇。
                    它的工作原理是,故事一開始,(mk-length mk-length)生成一個計算列表長度的函數,在其內部中,假如列表l為空,就返回長度為0;否則,就計算l的尾部長度,并加上頭結點的長度1,而計算l的尾部的函數,是通過(length length)來生成,其中length就是mk-length,故事就回到原點(mk-length mk-length)了,只是,其返回值在外圍中要加1了,然后,在更外圍中繼續加1,加1,……。
            但是,工作還沒有完成,因為,mk-length中,((length length) (cdr l))很刺眼,它應該是(length (cdr l))這樣的形式。重構,必須重構。必須在將其提煉成一個函數,因此,mk-length就變成
            (define mk-length
              (lambda (length-mk)
                ((lambda (length)
                (lambda (l)
                  (cond ((null? l) 0)
                    (else (+ 1 (length (cdr l)))))))
                 (lambda (x)
                   ((length-mk length-mk) x)))))
            代碼似乎變得復雜些了,但效果是一樣,并且,語法結構上基本保持一致。但是代碼好像的確變得更長了,這也沒辦發,為了保持最內部length的純潔性。但是,它也太深了,作為重點,應該放在外面,嗯,應該將兩個lambda對調一下。
            (define mk-length
              (lambda (length-mk)
                ((lambda (length)
                   (length (lambda (x)
                     ((length-mk length-mk) x))))
                 (lambda (length)
                   (lambda (l)
                 (cond ((null? l) 0)
                       (else (+ 1 (length (cdr l))))))))))
            面對著這么多的lambda,實在難以淡定。但必須接收洗禮,方可體會到函數作為一等公民,所帶來的強悍的表達能力,簡直能撞破習慣命令式編程的眼球。里面的lambda(length)又變回原來的樣子,但是,mk-length的主體已經不再是它了,而是一個以的lambda(length)為參數的lambda了。為了保持mk-length的純潔,繼續努力,這一次,是在兩個(mk-length mk-length)上做文章,每次都要寫兩個相同的函數,不如把它做成函數。事情到了這一步,Y組合子已呼之欲出。
            (define Y
              (lambda (f)
                (f f)))
            ((Y mk-length) '(a b c d e))    ;返回5
            然后將mk-length中的第一條length的lambda搬過來,并且作為兩個f的入參
            (define Y
              (lambda (length)
                ((lambda (f)
                   (f f))
                 (lambda (length-mk)
                   (length (lambda (x)
                     ((length-mk length-mk) x)))))))
            最后,將Y整得更加好看一點,也看來更加的通用,不僅僅是針對length,而是全部的需要遞歸的函數。
            (define (Y f)
              ((lambda (g) (g g))
               (lambda (g)
                 (f
                  (lambda (x) ((g g) x))))))
            再送上一道求和
            ((Y
              (lambda (sum)
                (lambda (n)
                  (cond ((= n 1) 1)
                    (else (+ n (sum (- n 1))))))))
             10)
            文章已經很長了,打住。以后再發揮吧。

            posted @ 2013-07-11 14:48 華夏之火 閱讀(2822) | 評論 (2)編輯 收藏

            C語言復雜聲明的本質與局限

                先簡單回顧一下C語言的獨有的變量聲明方式。自詡使用C語言多年,卻一直對于C的復雜的變量聲明方式頭皮發麻,直到看到VCZH大神前不久的大作,才恍然大悟。慚愧,因此下面的內容頗有拾人牙慧之嫌,但為了引出后面一系列關于語言的隨筆,也沒辦法了,本文的榮譽都歸于vczh大神。就從最簡單的說起。
                int a;    // 說明表達式a的值是int型,a自己本身也是int型,這不是廢話嗎?
                int array[N];    // 于是,表達式array[n]的值為int型,array是int數組,是否廢話的味道少了一點?
                int *pA;    // 顯然,*pA的值為int型,而pA的類型是指向int的指針。
                int fun(int x, int y)    // 毫無疑問,表達式fun(a,b)的值為int型,fun則是函數,其函數簽名是……
                通過前面例子,說明一個道理,可以從另外一個角度來理解C變量的類型聲明,先確定整個表達式的結果值的類型,再考察變量本身的類型。就好比以上幾個例子,a(單獨一個變量都是表達式), array[n], *pA, fun(a,b)這些表達式都是int型,定義變量的語句的類型,其實就是為了說明這個語句的變量的整個表達式的結果的值的類型。
                好了,請深呼吸,開始重口味了,下面的注釋,其實都是廢話。
                int *fuck[N];    // *func[n]的類型為int,因此,func[n]的結果類型為int*,因此,func的類型為數組,數組的元素為int的指針
                int (*pfuck)(int x, int y)    // (*pfuck)(a, b)的結果類型為int,看到(*pfuck),括號內出現一元操作符*,此物為求得指針的所指之內容,然后,此內容還能進行函數調用,因此可知,pfuck為指針,指向一函數,該函數的簽名是……。當然,表達式pfuck(a, b)也可以得到相同的結果,但是,為了強調pfuck的類型,請堅持使用(*pfuck)。
                int* (*pfuck)(int x, int y)    // *(*pfuck)(a, b)的值為int,pfuck的類型自然是函數指針,函數簽名是有兩個int型的參數,其返回值是int*
                int (*func[5])(int *p);    // 毋庸置疑,(*func[i])(int *p)的結果是int型。它表示先獲取數組的一個元素,對元素解引用,進而函數調用。顯然,func為長度5的數組,數組元素是函數指針,函數有一int*行的變量,返回值是int型。
                int *(*func())();    // 心里發麻是不是,要淡定。不管怎么樣,*(*func())()的結果始終都是int值,是不是?從最外圍上看,*(...)(),此乃一函數調用,然后對返回值解引用得到的值為int。我們知道,C語言中,只有兩物可進行函數調用的操作,或函數,或函數指針,兩者必居其一。有以上例子分析可知,*(*func)()此乃對函數指針的函數調用結果求指針值。現在,又有*(*func())();,括號內的*func(),分明就表示func的函數調用,此函數的返回值為指針。結合最外層的函數調用,此返回值指針指向一函數,也就是說,返回值是函數指針。因此表達式*(*func())(),涉及到兩個函數調用,它表示內層的函數調用返回函數指針,而此函數指針再調用一次,其結果為int*,再用上指針*運算符,整個表達式的值就為int了。因此,func是一函數,此函數返回函數指針,函數指針指向一個無參而返回值為int*的函數。曲折離奇,大功告成。

                好了,該反過來想了,如何從變量的類型來構造其定義語句。好比,“fuck指向一個數組,其個數為5,數組元素為函數指針,函數簽名為帶一個(int *p)參數,返回結果是int”。
                先考慮如何使用此變量。既然fuck是數組指針,那么,*fuck就是返回其所指向的數組,然后要得到數組的元素,自然理所當然必須用到[]操作符了,因此,就得到,(*fuck)[i]了,注意,千萬切記,必須加括號,否則,*fuck[i]意味著fuck自己本身就是數組了。自己本身是數組,和指向數組,也即,數組和數組指針的差別,是相當大的,其差別之大就好像整型類型和整形指針類型。然后,必須不能忘記的是,一元操作符*就是取得指針的所指之物。
                好了,總之,對于fuck,我們通過(*fuck)[i]得到數組元素。既然元素又是函數指針,進而就得到,(*(*fuck)[i])(pa),這個表達式的值為int。因此,答案就是,“int (*(*fuck)[5])(int *p);”。
                代碼寫成這樣子,真他媽的賤,盡玩文字游戲,寫的人費心,讀的人糊涂。這該死的C語言,shit!
                文章突然長了,打住。不惜對完整的類型進行分離,以求得聲明與使用的語法的高度一致性。C語言真是,真是精致得讓人大倒胃口。

                又:有時候,對于稍微復雜一點聲明的常用類型,會經常出現重復的聲明語法,特別是在函數指針的時候,為了擬補這種缺陷,或者說是痛苦,或者說是對于變量類型的重視,C語言提供了typedef的關鍵字。用以代表這種聲明與使用的一致性的變量的類型。在前面的例子中看到,聲明語句中的類型,只是說明變量采用這種表達式時,它的就是這種類型。好比,int *pArray[20],*pArray[i]的值為int型,但pArray卻絕不是int型,為了取得pArray的類型,可借助typedef;具體的使用如下,typedef int* IntArray[20];,然后,IntArray pArray;以定義同樣類型的變量。又好比上例,int *(*func())();這個函數聲明好像讓某些人難以理解,用上typedef化簡一下,就可以重點很突出了:
                typedef int* (*FunFuck)();    // FunFuck代表無參返回值是int*的函數指針類型;
                FunFuck func();    // 作用相當于int *(*func())(),但含義更加鮮明。
                可以看到,typedef的用法很簡單,不過是在過去的表達式的前面加一個typedef而已。后話,typedef在C++的template中,扮演了非常非常重要的角色,特別是模板元編程MPL中,全部的類型演算全部壓在它身上,其作用之大,簡直是驚天地泣鬼神,沒有了typedef,C++的template不過是普通簡單的泛型編程,有了template對typedef的完善支持,其實就是在struct/class內部中支持typedef語句,就導致了tmp的橫空出現,導致C++的template成為威力最恐懼,同時語法也是最恐懼的泛型語言,沒有之一。

                繼續補充:加上const。以上對于復雜聲明的理解,明眼人一看就知道僅僅是從右值的角度入手。要理解const,就必須不可不提到左值了。左值右值是C++中的基本概念,三言兩語也說不清楚。最粗淺的看法,左值指可以被寫入,也就是說能出現于賦值語句的左邊;右值指可讀,只能在賦值表達式的右邊。當然,一般來說,左值往往可以當做右值來使用,可寫往往意味著可讀。而const的作用,就是將原本可寫的東西,給整成只讀的了。具體到表達式來說,就是某些表達式的值具備左右值,而const就是去掉了它的左值功能。舉例說吧,還是從最簡單說起。
                int a; 表達式a的結果是int型,既是左值又是右值;
                const int a;,a返回的結果是int類型,但是此結果已不再可寫了,也即是a不能放在賦值語句的左邊了。
                int const a; 可這樣理解,先不理int const,a是一個變量,既可讀又可寫,const將a整成只讀。int表示const a的結果類型。雖然,理解上這樣,但對編譯器來說,const int a;和int const a;都一樣,都只是表達了同樣的意思,a是一個整型常量。
                const int *p;,*p結果為int型,加上const后,*p只能讀了,所以,p是整形指針,其所指的內容只能讀不能寫,但p本身卻可寫。 int const *p;,則先強調*p的只讀性,然后再說明*p為int型。其實,這兩種寫法的意思都一樣。
                int *const p;,const 緊挨著p,說明p本身只讀。至于 int *,則表示*p類型為int,可讀寫。因此,p是整形指針,p本身不可寫,但其所指的內容卻可讀又可寫。說實在,不明白這樣的指針變量有什么鬼用,實際的代碼應該用的很少才是。
                為了表達指針的只讀純潔性的概念,不僅指針本身不能寫,連其指向的內容也不可修改,C++終于整出了下面這樣偉大的代碼。int const *const p;或者const int * const p;。C++的這種做法,俗稱致力于解決臆想中的問題,因為const與指針的組合,實際上只有指針所指向的內容只能讀很有意義,其他的,意義微乎其微。
                可見,原本C的聲明使用一致性的語法,遇上const之后,開始有點混亂了。當半路中殺出C++的引用之后,這種語法的一致性在C++中就不復存在了。C++的很多語言特性,都在不同程度不同角度,深度和廣度上,形式和語義上,給C語法的精致性造成致命的各種各樣的沖擊。以至于,最后C++變成了有史以來很難很復雜超級變態恐怖的語言了,沒有之一。

            posted @ 2013-07-01 15:57 華夏之火 閱讀(2580) | 評論 (6)編輯 收藏

            鍵盤布局的改進之道

                  好久沒上博客了,自己的那么一點微末道行也不敢拿出來丟人現眼。實際上,過去的幾年,真的是讓C++和MFC害慘了,一直自個兒固步自封,說什么沒有透徹掌握它們,絕不碰其他的玩意,結果就悲劇了,眼界相當重要,再怎么夸張都不為過。顯然,MFC是垃圾,但實際上,C++也不是什么好菜,嗯,不吐槽了。還是做點更具實際意義的事情吧,今天的主角是鍵盤布局。
                  由于歷史的原因,當今流行的26字母的qwerty鍵盤布局并不是很科學,甚至有種說法,隨便弄一個布局,都要比qwerty好,只因qwerty當初的設計意圖就是為了最大限度的降低打字速度,這么說就有點過分了。不過,后來重新發明的布局,特別是DVORAK,的確比qwerty更具優勢。但是,不管DVORAK的先天設計多么合理,如何在市場上如何造勢,都不能撼動qwerty的主流地位。這很讓人無語,由此可見,技術并不是決定市場的首要因素,關鍵是先占領市場,形成標準,不管這個標準有多差,只要有很多人遵守執行就行了,好比XX紅色政黨,又好比MFC,現在VC2012上居然還有他的一席之地,中國計算機圖書還有那么多的VC書籍,實在令人疼心疾首。不過,本文的目的并非推薦DVORAK,當然,DVORAK鍵盤布局還是很值得廣泛使用,但是既然已經如此的熟悉qwerty鍵盤了,那么也沒有必要再訓練了,實際上,qwerty已經足夠日常使用了,我們平時打字,最大的限制在于大腦的速度,鍵盤布局到不是瓶頸。
                  我要說的是,對于碼農來說,鍵盤的另一不合理之處在于,小指的壓力過大,左小指還好,只需負責Esc、~、……、A、Z等11個鍵位,先不論esc太遠,這讓vim情何以堪,而無關緊要的大小寫切換鍵居然占據了那么優勢明顯好用之要地,等等無理設計。右小指表示壓力更大,起碼打了兩倍,因為它的掌管比左小指的大了一倍之多,幾乎是主鍵盤的1/4之多的鍵位,而且這些鍵,使用率都相當的頻繁,如果再加上上下左右方向鍵還有delete,這實在太無天理了。可憐的兩只小指,弱不禁風,嬌怯怯,卻要承受著生命難以承受之痛。相比之下,平時最能干的大拇指,居然只負責長長的空格鍵和兩只alt這三個,這種不合理不公平的待遇,不禁讓人怒從心頭起,惡向膽邊生,必須改革,徹底改革。給小指減負,給大拇指加負。
                  當然,改革之前,先介紹windows下兩大偷天換日的鍵盤修改利器,autohotkey和keytweak,其性能和使用說明,請各位自行百度谷歌。為了達到目的,老夫真的是挖空心思,無所不用其極。方法如下:
                  1、借助autohotkey,將右手的所有鍵位都往右挪一格,也就是說,原來的7ujm這4個鍵,被發配到8ik,上,而8ik,就到了9ol.上,其他的以此類推,至于最右邊的=\'/就只好屈居于開始時的7ujm上了。這樣一來,小指起碼少按了4個鍵位。右拇指只要愿意,可以不費力的按到右win鍵了,現在,兩只拇指終于可以掌控四個鍵位了,四個很重要的鍵位,恩,目前除了space,其他三個似乎沒啥特別,但很快,就會看到剩下來的三個中的其中一個,將發光發熱,照耀整個鍵盤,最有作用。
                  2、眾所周知,上下左右home end 和翻頁,這些鍵,其實也很重要,但是要按到它們,必須跑大老遠,揮動右手做大幅度的機械運動。以至于,在vim和emacs中,都有各自的快捷方式來實現同樣的功能。什么hjkl,什么ctrl+n,ctrl+p等等,不一而足,這種快捷方式,居然是其優于其他編輯器的亮點之一。但是,上下左右等鍵位可以配上ctrl、shift、win修飾鍵,然后馬上就可以做出很多種組合,當然,emacs和vim也真是神通廣大,針對每種組合,基本上都有對應的快捷鍵,只是記憶起來,實在麻煩。而區區在下,還曾經吭哧吭哧的拼命記憶過。好吧,好不容易習慣了vim和emacs那套逆天指法,卻發現只能在vim或者emacs的環境下使用,屠龍之技,屠龍之技。我們要求的是,能夠有一套放之于四海而皆準的指法,可以在所有的軟件下,所有的場合下都發光發熱。
                  可能嗎?確實有辦法,就是在新鍵盤布局下,將右拇指能比之前輕松的按到的alt,搖身一變,變成換擋鍵,只要此鍵一按,馬上wsad(游戲模式下的上下左右方向鍵)就變成上下左右了,配合jkl就可以組合出ctrl shift alt等效果。ec為home、end,rv則是上下翻頁,f為esc鍵,各種各樣,何其方便哉!剩下來的問題,就是右alt該何去該從,很簡單,鵲巢鳩占,老實不客氣,就占到右win鍵上,至于右win鍵怎么辦,該怎么方便就怎么方便,甚至不存在,也沒關系。制造換擋鍵,必須用到keytweak,autohotkey是不行的,鑒于數字小鍵盤上的除號實在很少用到,因此就拿他來當犧牲品了。其他的種種,請大家參考隨文附上的ahk腳本。
                  這樣一來,只要稍加訓練,鍵盤用起來將會很爽了。不爽的是,用別人的電腦,將特別的不習慣,各種難受。
                  此外,右ctrl,可以用右掌腹來按,不必煩勞小指,他已經夠累了。然后,漢字輸入,要用拼音,最好是雙拼,切記切記。不要在用什么五筆了,那是特別落后的輸入法,其令人發指之處,可以和mfc值得一拼。在下曾經是五筆高手,下過苦功夫,多少個夏天夜晚,揮汗如雨,苦練五筆,一分鐘達到百多字,絕對有資格說五筆的不是。現在我用雙拼很高興,已經不記得五筆的很多字根了,才不到半年的時間。
                  其實,我都努力過,只是,都把汗水和精力,放在垃圾上了。C++是垃圾嗎,當然不是了,但實際上,……,我覺得C++可以和糞便有得一比,作為肥料,還是很好地。

            7::=
            8::7
            9::8
            0::9
            -::0
            =::-

            u::y
            i::u
            o::i
            p::o
            [::p
            ]::[
            \::]

            j::h
            k::j
            l::k
            SC27::l
            '::SC27

            m::n
            ,::m
            .::,
            /::.

            y::\
            h::'
            n::/

            NumpadAdd::=
            Shift & NumpadDel:: Send, {Backspace}

            NumpadDiv & a:: Send, {Left}
            NumpadDiv & d:: Send, {Right}
            NumpadDiv & w:: Send, {Up}
            NumpadDiv & s:: Send, {Down}

            NumpadDiv & e:: Send, {Home}
            NumpadDiv & c:: Send, {End}
            NumpadDiv & r:: Send, {PGUP}
            NumpadDiv & v:: Send, {PGDN}

            NumpadDiv & z:: Send, {BackSpace}
            NumpadDiv & x:: Send, {Delete}
            NumpadDiv & f:: Send, {Escape}

            CapsLock::LControl
            LControl::Esc
            Esc::CapsLock

            NumpadDiv & k::
                Send {Control down}
            KeyWait k  ; 等待用戶釋放按鍵.
                Send {Control up}
            return

            NumpadDiv & Shift::
                Send {Shift down}
            KeyWait Shift  ; 等待用戶釋放按鍵.
                Send {Shift up}
            return

            NumpadDiv & Control::
                Send {Control down}
            KeyWait Control  ; 等待用戶釋放按鍵.
                Send {Control up}
            return

            NumpadDiv & Alt::
                Send {Alt down}
            KeyWait Alt  ; 等待用戶釋放按鍵.
                Send {Alt up}
            return

            NumpadDiv & #::
                Send {Win down}
            KeyWait #  ; 等待用戶釋放按鍵.
                Send {Win up}
            return

            NumpadDiv & l::
                Send {Shift down}
            KeyWait l  ; 等待用戶釋放按鍵.
                Send {Shift up}
            return

            NumpadDiv & SC27::
                Send {Alt down}
            KeyWait SC27  ; 等待用戶釋放按鍵.
                Send {Alt up}
            return

            NumpadDiv & '::
                Send {LWin down}
            KeyWait '  ; 等待用戶釋放按鍵.
                Send {LWin up}
            return

            posted @ 2013-06-29 02:56 華夏之火 閱讀(2528) | 評論 (4)編輯 收藏

            一點反省

                好久沒有上博客了,再次看看之前的文章了,覺得很難受,除了批評MFC的那篇有點意義之外,其他都是在放屁,如果誤人子弟了,在下很不安,并且里面還有很情緒化的傾向,本來應該刪除,免得繼續禍害初學者。但是,應該勇于面對自己曾經犯過的錯誤,就讓它留著吧,只是祈求后來者,不要再看了。
                有一種錯誤的認識,說什么編程語言不重要,編程思想才重要,這種認識很沒有意義,何為編程思想,只怕說這句話的人也不是很清楚,至于編程思想包含了哪些內容,那更加是她沒法想象得到的廣闊天地。當然,思想很重要,但是,無論多么高妙的思想,終究還得靠語言來表達。而且,有些語言表達某些思想,就是要比其他語言要直接,要直白,并且所謂的思想的有些理念本身就是語言的重要組成部分,這種語言還要求學習者要經歷語言的洗禮,從而盡快的掌握所謂的編程思想,以便能初步使用它。然后,又有些語言天生殘疾,無論如何整頓,就是沒法表達某種編程思想,好比JAVA。總之,語言的選擇,應該是很嚴肅的問題,不可等閑視之,至于思想,那也是通過具體的語言才能領悟。
                不管對于C++有多么深厚的感情,但是,確實沒必要讓自己吊死在一個樹上。走出了那片天地,開闊視野之后,再回來,可以更好更快的處理原來領域上的問題。所以對于有志于CPPER,就是不要再C++各種語言細節上死鉆牛角尖,必須多學習幾種語言,吸收他們的優點,然后回來擬補C++本身的很多不足,學習其他語言,其實也是在學習C++,并且是更好地學習之法,因為這樣,才能了解到C++的不足,才知道為了擬補其先天的不足,是怎樣的努力,才設計出那些逆天的語言復雜性。
                個人感覺,C語言弱智(將太多的事情交給程序員來做,美其名曰信任程序員,搞得碼農一天到晚死鉆細節,又由于語言的抽象能力嚴重缺乏,所以很多事情就是吃力不討好。當然也要承認,人寫出來的代碼,始終要比編譯器出來的要好要可控,起碼某種形式上,但是有必要嗎)。至于C++,的確強大,自由,但是,她真的是丑陋無比,由于本身的野心過大,導致其語言核心無比的模糊,想要很好的使用,必須深刻系統的學習其靜態類型系統和各種類型演算,因此,模板元編程必須基礎十分扎實,不是為了用MPL寫代碼,而是學了之后,才能更深刻的感受到C++的類型系統(某些人十分反感template,不知所謂,殊不知,沒有了template,C++的威力將降低一大半,還有,C++的各種奇技淫巧和宏,也在template這里大放驚人異彩。學了C#和java的泛型之后,才驚詫C++的template的功能如此的變態厲害,所謂的圖靈完備,原來是這么回事)。此外,還要知道C++的種種不足,比如GC,比如嚴重殘缺的動態類型信息,比如……(好吧,想不出來了),但是,這些都不是問題,C++非常神奇,對于語言上的種種不足,通過有些人種種奇技淫巧,歷經千辛萬苦,終于可以實現了,但是,最后出來的東西,始終還是沒有語言層面上直接支持的來的好,并且有些還不免很丑陋,這是必須的,不過,C++11出來之后,語言的各種不足,都有不同程度的改善,造輪子時可用的材料也多了,最后,得益于C++11的新特性,輪子的外觀也可以更好看。
                C++的學習教條,當然,重中之重,是,懇請不要學習C++,現實的很多很多問題,都可以不必C++來搞,那些嵌入式的東西,與硬件緊密相關的,C其實很能夠勝任了,其實,C還是很好地。
                1、忌將精力都放在C++上,多學習學習其他語言;2、忌學MFC,這個東西,碰都不要碰;3、不要用C++解決所有的問題,很多問題用其他語言來解決效果會更好,無論開發效率還是運行效率;4、眼界很重要,……。作為C++的死忠,說這些實在有悖于對C++的感情,其實也不是,只是曲線救國之法,因為,要駕馭C++的種種奇怪的復雜,的確需要C++以外的視野。C++的確不是人人都能使用,合格的C++猿不但要擁抱奇技淫巧,還要能發明出更好的奇技淫巧,沒有最好的奇技淫巧,只有更好的奇技淫巧,C++就是用來創造奇技淫巧的。
                竊以為,對于語言學習者來說,特別是C++猿,以下幾種,C#的實用,scheme的優雅,haskell的嚴謹簡潔,都有必要的學習學習。對了,還有smalltalk,但是考慮到面向對象本身的問題,就不推薦了。至于其他語言,在下接觸不深,就不敢多嘴了。對了,還有設計模式,這東西還是很好的,特別是在面向對象的時候。至于以前說到的消息發送,純屬無稽之談,這東西反人類,那是由于C本身的抽象能力不足,才搞出來的一個怪胎,無奈之舉。對于比C更高級的語言,完全沒必要再模仿了。設計模式的一大特點在于把模式的實現完全固守在靜態類對象上,這樣理解起來確實方便,但是,帶來的問題,就誤導了某些無知的讀者,以為必須一定要用靜態的面向對象的語言(在這一點上,JAVA是最徹底的實踐者)來實現模式,以至于,為了實現模式,要寫很多很多不必要的狗屎般的代碼。其實,設計模式只是思想,書中的實現只是示范而已,大伙兒不必死盤硬套。對了,對于C++沉思錄那道著名的例題,用設計模式做出來的效果,始終感覺很別扭,較好的思路是組合運算子。
                互聯網上的有害信息真多,很不幸的是,之前寫了那么幾篇狗屁不通的文章,實在慚愧。

            posted @ 2013-04-28 22:55 華夏之火 閱讀(510) | 評論 (2)編輯 收藏

            非理性擁護C++

                    本來只想對C++贊嘆復贊嘆,后來就失控了,接著情緒化了,最后終于開始爆走,語無倫次。
                    平心而論,C的而且確小巧精致,一切通通透透。老夫真心喜歡用它來編碼,但一旦動用真格了,就立馬葉公好龍,就會懷念C++的種種好處,class、 template、 virtual、 析構函數、甚至異常、const、引用等等,原來,離開了之后,才明白你的種種美妙動人之處,因此,朕已決定,有生之年,假如還在編碼,那么C++,在心目中的,將是無可替代,它的一切,即便缺點,也是那么地令人回味無窮。因為它的一切,將自由貫徹到底,充分尊重用戶的選擇,不輕易剝奪用戶的權利,更不強求用戶用什么樣的方式做設計。所謂自由的世界,獨立的人格,手持C++利器,雖不敢說橫行天下,但起碼能愉快地編碼。只有C++,當一個人獨立使用,如此的耐人尋味,歷久常新。多人一塊開發,簡直是大災難,沒必要的封裝,種種自制的破爛輪子(前幾年,出自本座手中的輪子不計其數,基本上慘不忍睹),錯綜復雜,交叉引用的類關系。這在其他語言中難以出現的怪現象,在C++中,平常得很,再一次證明了C++的博大精深,包羅萬象。不說別的,就說C++中的最負盛名GUI框架MFC,其類層次的設計,糟糕透頂,而BCG的代碼注入,毫無創意,笨拙無比的命名,垃圾般狗屎般的代碼堆積,可怕的內存消耗,令人眼界大開,MFC的資源消耗已經夠厲害,相比之下,居然顯得那么節儉,而用BCG開發界面,居然比C#又或者JAVA做出來的軟件,還不卡,這一切,都證明了C++過人之處。愛死你了,C++。
                      近幾年來看到某些人不知出于何因,對C++橫加指責,說什么論效率不如C,論高級特性又不如其他的動態語言,實在莫明奇妙。說什么C++中的inline、繼承、template破壞了模塊的分離,“用C語言1000行源碼能完成的工作千萬不要用C++重寫!”,實則用C++來寫根本就無須1000行,并且可以精簡那些字數多的代碼行,并且還更加易讀易懂,更加容易維護,效率或許還能更快一點點,得益于內聯。如果還覺得用C++寫1000行代碼沒有C那么漂亮,那只證明閣下沒能力駕馭C++,請不要對C++亂加指責。他們那些所謂的C高手的代碼,到處指針飛舞,又長又臭一再重復的表達式(本該內聯大顯身手),著實讓人難受,當然,不否認他們的精妙設計。
                    縱觀他們對C++非議之例子,無一不暴露出其設計上的缺陷,本該成員函數指針大顯伸手,他們卻用上了虛函數;Template模式的函數(順序依次,調用幾本虛函數),本該做成全局函數,硬是整成員函數;多繼承中的鉆石抽象基類不該有任何東西,他們卻偏要放某些東西,最后沒辦法,在虛繼承中糾結。……所有這一切根本無損于C++,卻只顯現出他們的愚蠢與無知。想展現自己也言行獨立,到頭來卻做出拾人牙蠢之事。其實,他們更應該感謝C++,是C++的包容,才容許了如此丑陋的設計。本座平生最不齒這群宵小,自己毫無主見,風聞名人幾句驚世駭俗之話語,就跟著瞎起哄,國人的毫無道理的盲目跟風,由來已久,也不必細表了。那些所謂的C高手,覺得用C能做出精妙的設計,為何用起C++就不行了,其實他們大可“用C做設計,用C++編碼”,這樣,根本就不會影響他們的偉大杰作構思。
            并且要做到如同C那樣的高效,C++中完全沒有問題,完全可以放下身段,將C++的抽象降低到C那樣的級別,在沒有獨立完整的概念之前,或者是沒有很好的理由,絕不用類來封裝代碼,禁用慎用C++的一切高級特性,好比虛函數、繼承、異常等。任何語言特性都可以寫出垃圾代碼,也容易用得不好,但不可因為這樣,就否定此種特性的價值。特性作用越大,就越微妙,就越容易濫用誤用。即此而觀,C++中,應該以class最為難用,此關一過,必定神清氣爽。
            的確,C中,你可以也必須面對一切細節,在這種惡劣的環境下,手上能用的武器,也只有函數、結構體、數組和宏,程序員的潛能就這樣被迫出來,爆發出來了,做出最合乎本質的設計,而這幾樣簡單武器,互相組合,居然可以用得如此出神入化,其效果鬼斧神工,巧奪天工,直可驚天地,泣鬼神,手法更是精彩繽紛,巧妙絕倫,令人目不接暇,但是,不管如何,始終缺乏管理細節的有效武器。
                   鄙人最驚嘆C++的一強悍之處,對于各種匪夷所思的變態問題,會有更加變態的解決方式,而且還不止一兩種,更可見其靈活多變自由豐富的個性,但眾多迥異特性又能如此和諧的共存,為什么?竊以為C++是強類型的靜態語言,雖然提供多種語言工具以讓碼農愉快輕松地編碼,盡可能地在編譯時期發現更多錯誤,各種微妙的語言特性不過是為了幫助碼農愉快高效地編碼,少出錯,他們可以用這些語言工具整理組織C的各種凌散的表達式。
            因為C中雖然能直面一切細節,卻缺乏管理細節的語言工具。所有C中的細節,幾乎可通過C++的各種豐富特性妥善整理,而效率的損失又甚少,并且,在其強大的靜態系統的分析,能多發現點問題。但是強類型只是工具而已,必須善加利用,但C++的碼農不會受束縛,必要的時候,大可突破。鄙人就曾經實現了一個微型的動態系統,對象之間沒有用層次關系,都是平等的,但之間又能互相組合裝配拆除,達到多繼承的效果,又沒有多繼承的各種問題。雖然語法上別扭點,但習慣了就感覺挺不錯。
                   要看到C++的對C代碼的變態重組,為此,隨便舉例,qsort是代碼上的典范境界,能排序所有的數組,只要提供了元素之間的比較函數,就能快速地排序,實至名歸。但它是弱類型,其正確性全靠程序猿手工輸入,參數出錯了,編譯器也檢查不出來,當然C高猿不大容易出錯。只是,依賴于C++強大類型推導威力,通過template整成以下樣子,既不限制qsort的包容性,又不損失任何一點點效率
            template<typename _Ty>
            inline void Sort(_Ty* pItems, size_t nItemCount, int (__cdecl* funcCompare)(const _Ty&, const _Ty&))
            {
                int (__cdecl * _PtFuncCompare)(const void *, const void *);
                union_cast(_PtFuncCompare, funcCompare);    // 為忽弄編譯器的強類型檢查
                qsort(pItems, nItemCount, sizeof(_Ty), _PtFuncCompare);
            }
             但已經是強類型的了,C++猿用起來就不大容易出錯了,并且元素的比較函數也更加容易編寫,沒必要再用指針了,個人而言,引用比指針好,最起碼少敲一下鍵盤,那行代碼的長度可減少了一個字符。這樣,用起來不是更爽嗎?
                  又好比消息循環,判斷消息類型,一遍又一遍地寫著重復的表達式,好比,msg.message==WM_LBUTTONDOWN,不好玩,干脆class一CMsg,繼承自MSG。好比這樣:
            class CMsg : public MSG
            {
            public:
                bool Is(DWORD nMsg) const{ return message==nMsg; }
            };
                     于是以上的那行判斷語句,就精簡成msg.Is(WM_LBUTTONDOWN),感覺應該好點吧。這兩例的代碼整理手段,對C++來說稀松平常,但C中就做不出來了,大概也只能用宏了,但宏的問題,大家也知道。
                    又有人說,C++高手的修成要經過兩次轉換,從C到C++,然后從C++回復C,實在異想天開,不值一曬,舍棄C++的強大類型檢查,欲與一切細節肉博,吾不見其高明。這不是什么C++高手,充其量也只是C高手,其苦心孤詣在C中模仿C++的面向對象的伎倆,用C++來表達,不過小菜一碟,并且還不失強類型檢查,必要時,只須用聯合體或類型轉換忽悠編譯器。那些回歸C的高猿的C++代碼,其實,不甚精致。所以,大家也不必理會。只須老老實實地做出簡簡單單的設計,然后再用C++組織管理各種細節,大可將代碼寫得漂漂亮亮干干凈凈。
                     要謹記的是,只用那些熟悉有把握的語言特性,對于每一個用到的C++關鍵字,一定要清楚其背后的機制并且由此所帶來的各種副作用。最難用的就是class了,毫無必要的封裝, 比赤裸裸的代碼更加丑陋,請優先選擇非成員函數。封裝的出現,是因為代碼的一再重復出現的需要,而并非想當然地推理演繹。只要是重復代碼,不管是一行表達,連續多行,分散跨行,都可以給予包裝在一起,只需一個函數調用。
                      再次重溫C++的核心設計,盡可能利用靜態強類型,盡可能地在編譯期中找出程序的錯誤,提供多種豐富特性,協助碼農充分地發揮強類型的一切優點,對抗一切細節,對抗一切重復代碼,并且不必付出任何不必要的代價。當然,強類型只是忠實的奴仆,完全不必因為它而遷就你的設計,想要忽悠它,方法多種多樣。 有人說,C++的語言特性太凌散,不系統,好像打補丁似的。但鄙人覺得挺好的,特性分散,各自為政,可隨意自由組合,你討厭某個特性,大可不必理睬,它就靜靜地站在一旁,絲毫不影響你的代碼,這不就是設計的最高境界嗎。
                    好了,終于狠狠地出了口惡氣。在下承認很情緒化,有失高手風范。

            posted @ 2012-11-21 12:00 華夏之火 閱讀(2644) | 評論 (16)編輯 收藏

            略說成員函數指針及其模板的精簡實現

                請容許先發一句牢騷,“這萬惡的成員函數指針的丑陋語法!”,C中的函數指針的語法已經夠難看的了,但相比之下,成員函數指針卻更加不堪入目,使用上又很不方便,很不人性化,簡直是只能行走寸步。只可惜,函數指針的作用實在太大了,忽視不得。
                大家都知道,函數指針(或又叫回調函數)是上層模塊和底層模塊進行通信的最佳手段,上層通過提供函數指針給底層,以使得底層在適當的時候,可以調用執行上層的代碼,C庫中的qsort足以說明這種使用,qsort在排序時,不知道如何比較兩個數組元素的大小,必須通過上層提供的大小比較函數來進行比較。此外,操作系統提供的API中,有多少地方使用上了回調函數。假如沒有函數指針,這簡直沒法想像,日子沒法過了。函數指針是實現模塊分層的不二法門,當然接口也可以,但是,用戶代碼必須繼承或者實現底層硬性規定無理取鬧的虛函數,本來很輕量級的POD,再也不輕快了,實在頗不方便,不,簡直很有點惡心。說來說去,還是函數指針好。
                既然,C中的回調函數這么重要,那么,可想而知,進入C++中,整個世界到處都是CLASS,將回調函數這個概念推廣到CLASS上,也即是成員函數指針,將是多么迫切的事情。理論上,可以將成員函數指針視為函數指針的語法糖,只要規定函數指針的第一個參數為void* pThis,然后在函數指針的實現函數中,進行類型轉換也能滿足使用,在很長的一段時間里,因為這種方式的簡單清晰,一直都用這種方式代替成員函數指針。但是,一遍又一遍地被迫編寫重復代碼,特別是枯燥的類型轉換,任何人都無法忍受。因此,決定直面這個問題。
                理論上,成員函數和普通函數一樣,在內存中,都有自己的位置,只要有了地址信息,就可以通過指針來獲取,保存起來,然后在未來的某個地方,通過這個指針來執行函數中的代碼。差別之處,在于,調用成員函數前,要先將this推入ecx中,很久之前,成員函數指針確實和普通函數指針一樣簡單,只是后來,虛函數和多繼承出現之后,簡單的指針信息再也承載不了成員函數的重量,從此之后,.......(忽略,請大家自行BAIDU)。總之,C++中,成員函數指針并非指針類型,理所當然,也就不支持指針的大多數操作,比如,賦值NULL,或者類型轉換。因此,所有能夠讓函數指針大放異彩的種種手段,在這里,都用不上了,原本在C中光芒四射的好東西,到了C++中,竟然黯然失色,所有本該讓函數指針大顯身手的地方,大家都繞道而行。
                逼急了,也有嘗試突破,MFC的僅僅作了有限爭取的手段(為了這一點點好處,MFC可不知作了多大的努力),居然成為其消息映射的基石。但是,據說,MFC的成員函數指針的設計也非出于自愿,而是因為消息太多,實在沒法整成虛函數來處理,每個窗口類背負成千上萬個函數的虛函數表,可不是省油的燈。為了努力地支持虛函數和多繼承,C++的編譯器不惜在成員函數指針的使用上設下種種阻攔,令人又氣又恨。而更加令人不解的是,C++橫行天下十幾年,函數指針似乎長期得不到重視,大師們都在面向對象上探索,很多本該成員函數指針發光發熱的地方,幾乎都退位給虛函數了,并美其名曰策略模式又或者是其他的什么模式,不過是換了一套更加難看的馬甲,卻又那么好聽的名字,不,不好聽,只要聽到模式兩字,就令人大倒胃口。所有大用特用模式的代碼,如果用非模式來實現,其代碼量將少掉很多,而且還更具擴展性,這是真的。先透露一下,正在構思一文章,將深度介紹模式,專注于WHY,并且類比現實,兼扯上WINDOWS、COM和MFC對模式的應用,說句良心話,如果只用接口來做設計,模式絕對是好東西。只可惜,接口其實是SB。寫底層代碼,如果要求用戶必須實現某些接口,又或者是繼承某些類,改寫虛函數,這種侵入式的設計,實在無理取鬧之至。
                后來,大伙兒也終于開始重視成員函數指針,特別是C#的委托出現之后,網絡上更是充斥著各種成員函數指針的版本代碼,都可以很好地完成任務。特別是TR1又或者是BOOST中的function,功能相當的強悍得令人非大吃一驚不可。只可惜,大多數情況下,用戶只想填飽肚子而已,但是BOOST或者其他的類庫卻硬要來一桌滿漢全席,這也罷了,但是,它還要用戶額外買單,并且還真不低呢,這就很讓人受不了啦。其實,一般情況下,我們的要求不會太過分,僅僅想要針對普通的成員函數指針進行回調,它卻為此在其內部new一個內部類出來,假如大規模使用,后果將不堪設想,其實也沒那么嚴重,完成是C++迷們的強迫癥。
                但是,說真的,實在希望很精簡,不要生成不必要的虛函數表,不要模板生成不必要的函數(沒辦法內聯,有函數地址的那一種),只要求它如同對待C中的函數指針一樣,參數入棧和一個簡單的函數調用的指令,外加將this推入ecx即可,就好像直接調用成員函數那樣就好了。好了,貢獻上代碼了,史上最輕量級,精簡無比的成員函數指針,功能也最弱了。對不起,代碼并不完整,實際的代碼,用上了宏,所謂的宏的圖靈完備。在下很懶,一向只介紹想法而已,只于具體的實現細節以及語法考究,竊以為,每個C++迷們應該完全能夠勝任。俗話說,高手只要求創意就行了,本文詳細介紹算法并給出代碼,已經落了下乘。
                這個實現,不考慮多繼承,忽略了虛函數,也不支持非成員函數(也可以用上,只是,要多做一點點手腳,以下將給出示例,而且,普通函數指針已經完全可以勝任),只集中火力專注于普通成員函數,畢竟,在下的運用中,它占上了95%以上,所以才能如此的高效。單一職責啊!
                此外,本文參考了《成員函數指針與高性能的C++委托》、《劉未鵬的BOOST源碼解析》、《C++設計新思維》,請自行GOOGLE。
            template <class OutputClass, class InputClass>
            union horrible_union{
                OutputClass 
            out;
                InputClass 
            in;
            };

            template 
            <class OutputClass, class InputClass>
            inline 
            void union_cast(OutputClass& outconst InputClass input){
                horrible_union
            <OutputClass, InputClass> u;
                typedef 
            int ERROR_CantUseHorrible_cast[sizeof(InputClass)==sizeof(u) 
                    
            && sizeof(InputClass)==sizeof(OutputClass) ? 1 : -1];
                u.
            in = input;
                
            out = u.out;
            }

            template
            <typename FuncSignature>class TMemFn;
            class CCallbackObject{};

            template
            <typename R>
            class TMemFn<R ()>
            {
            public:
                typedef R ReturnType;
                ReturnType 
            operator()() const{return (pThis->*func)();}

                template
            <typename _Ty>
                
            void Bind(_Ty* pObj, R(_Ty::*proc)())
                {
                    union_cast(pThis, pObj);
                    union_cast(func, proc);
                }

            public:
                typedef ReturnType (CCallbackObject::
            *FuncType)();
                FuncType func;
                CCallbackObject
            * pThis;
            };

            template
            <typename R, typename P1>
            class TMemFn<R (P1)>
            {
            public:
                typedef R ReturnType;
                typedef P1 Param1Type;

                ReturnType 
            operator()(Param1Type param1) const{return (pThis->*func)(param1);}

                template
            <typename _Ty>
                
            void Bind(_Ty* pObj, ReturnType(_Ty::*proc)(Param1Type))
                {
                    union_cast(pThis, pObj);
                    union_cast(func, proc);
                }

            public:
                typedef ReturnType (CCallbackObject::
            *FuncType)(Param1Type);
                FuncType func;
                CCallbackObject
            * pThis;
            };

            template
            <typename R, typename _Ty>
            TMemFn
            <R ()> MakeMF(_Ty* pThis, R(_Ty::*proc)())
            {
                TMemFn
            <R ()> res; res.Bind(pThis, proc);
                
            return res;
            }

            template
            <typename R, typename _Ty, typename P1>
            TMemFn
            <R (P1)> MakeMF(_Ty* pThis, R(_Ty::*proc)(P1))
            {
                TMemFn
            <R (P1)> res;res.Bind(pThis, proc);
                
            return res;
            }

            int Test(int a)
            {
                printf(
            "Hello World %d\n", a);
                
            return a;
            }

            int _tmain(int argc, _TCHAR* argv[])
            {
                
            class _CTest
                {
                
            public:
                    
            int CallTest(int a)
                    {
                        
            return Test(a);
                    }
                };
                TMemFn
            <int (int)> aTest = MakeMF((_CTest*)NULL, &_CTest::CallTest);
                aTest(
            80);
                
            return 0;
            }

            posted @ 2012-11-16 10:46 華夏之火 閱讀(2939) | 評論 (4)編輯 收藏

            輕量級共享對象的靈巧指針的實現

                毫無疑問,shared_ptr的功能不可謂不強大,設計不可謂不精巧,它的抽象級別不是一般的高,不僅要管理一般的C++內存資源,更染指其他的非C++資源,比如文件、比如連接、……,只要給它一個支點(釋放資源的函數),不僅如此,還能頑強地生存于各種惡劣的環境,好比多線程、引用循環。當然,代價是有的,它背地里做了很多不為人知的勾當,表面上僅僅一行的帶有構造函數shared_ptr的定義代碼,編譯器卻要很無奈地生成一個莫明其妙的多態模板類(_Ref_count_base的繼承類,帶有虛函數表,意味著不能內聯,用以在恰當的時機,釋放資源),更別提要多了一堆指令,當然,在當今硬件性能蓬勃發展的美好時代,這點代價根本就不算什么,比之于那些什么所謂的虛擬機,甚至可以忽略不計。但是,總是有那么一批老古董,總會強迫假想自己寫的程序會運行于各種資源非常苛刻的環境下,內心就是沒法原諒shared_ptr所帶來的極細微的損失。好比區區在下,每一次一用到shared_ptr,心里的那種負罪感啊,又多了幾條廢指令,又浪費多了十幾個的堆字節,是否將生成內存碎片啊。終于有一刻頂不住了啦,去你媽的shared_ptr,老子不過想讓你老老實實的代理內存資源,本本分分地做好你的分內之事,不勞你費心照顧其他的系統資源對象,那些場合本座自然有更好的解決方式。于是,制造輪子的悲劇又再次誕生了,雖然,他們一直在內心深處抵制新輪子的愚蠢行為,但是,……,只能說,知我者謂我心憂,不知我者謂我何求。
                每次想到shared_ptr要new一個_Ref_count_base的對象來管理計數,有人就恨得牙根發癢,巴不得把_Ref_count_base的數據成員搬過來,放之于那個要管理的對象的身上,以減少一小塊內存。假如,客戶傳到shared_ptr構造函數的指針,此指針所指的內存,能再多幾個字節(一個字節也行,最大值255,已足矣),以供我等存放一個long型的計數器,那就好辦了。白癡也知道,這是不可能的事情。除非,此對象由shared_ptr來構造,那么還有辦法再放多點額外內存進去。此話怎講?大家都知道,C++中, new一個對象時,即意味著兩步操作:1、分配一塊內存;2、在此塊內存上執行對象的構造函數。如果第1步的分配內存,能作多點手腳,比如說,分配一塊比對象本身所占空間還要大的內存,那么我們的shared_ptr就可以把計數器放進對象之中了,也無須再new一個新的_Ref_count_base對象來管理計數器了。兩塊內存,合二為一,雙劍合璧,妙哉妙哉。但,這如何做到呢?
                以下,是一個類從簡單到復雜的物種進化歷程。C++中,只要是通用類,即使再簡單的需求,要寫得可以被普遍承認,可以高高興興地到處使用,都絕非易事。而且,更悲劇的是,辛辛苦苦,嘔心瀝血造出來的輪子,還很有可能一問世就直接被槍斃,就算能茍且活下來,也不會有車愿意組裝這一個廢輪子。
                廢話不說,書接上文,很明顯,對象的new操作應該由我們的shared_ptr來掌控。任由用戶來new,就太遲了,對象的內存塊已經確定下來了,沒文章可做啦。換句話說,shared_ptr必須模擬標準的new的兩在操作分配內存和調用構造函數。由此可知,以下所探討的shared_ptr運用場合也很有限,只適合于那些能看到構造函數并且知道其大小的C++類,所以,請大伙兒不要抱怨。唯其需求簡單明確,所以才能高效。
            首先,用一結構體__SharedObject來包含計數器和對象,如下所示:
            struct __SharedObject
            {
                
            void* Object()    // 返回對象的地址,由于不知對象的類型,所以只能是void*,表示內存地址
                { return this+1; }
               
            long Incref() { return InterlockedIncrement(&m_nRef); }
                
            long Decref() { return InterlockedDecrement(&m_nRef); }
               
            long m_nRef;
            };
                是否很簡陋,本座沒法也不想把它整得更加好看了。
                我們的shared_ptr,就暫時叫TShared_Ptr好了,其包含的數據成員,暫時很簡單。就只有一個__SharedObject的指針而已,后面由于多繼承多態的原因,將被迫多增加一個指針。
                好了,先看看TShared_Ptr的使用之道,此乃class template。由于共享對象由TShared_Ptr所造,所以,在其誕生之前,首先勢必定義一TShared_Ptr變量,好比,TShared_Ptr<int> pInt;考慮TShared_Ptr的構造函數,如果在里面就急急忙忙給共享對象分配內存,將沒法表達空指針這個概念,所以它的無參構造函數只是簡單地將m_pShared置為NULL。然后,TShared_Ptr必須提供分配內存并執行構造函數的操作,叫Construct吧;然后,析構函數也絕不含糊,執行對象的析構函數并釋放內存。于是,TShared_Ptr的基本代碼就出爐了。
            template<typename _Ty>
            class TShared_Ptr
            {
            public:
                TShared_Ptr() {m_pShared 
            = NULL; }
                TShared_Ptr(
            const TShared_Ptr& _Other)
                {    
                    
            if (m_pShared != NULL)
                    {
                        m_pShared 
            = const_cast<__SharedObject*>(_Other.m_pShared);
                        m_pShared
            ->Incref();
                    }
                    
            else
                        m_pShared 
            = NULL;
                }

                
            ~TShared_Ptr()
                {
                    
            if (m_pShared != NULL)
                    {
                        
            if (m_pShared->Decref() <= 0)
                        {
                            
            if (m_pShared->m_nRef == 0)
                                DestroyPtr(
            get());
                            free(m_pShared);
                        }
                    }
                }
                _Ty
            & operator*() const _THROW0() { return *get(); }
                _Ty 
            *operator->() const _THROW0(){return (get());}

                
            void Construct()
                {
                    ::
            new (m_pShared->Object()) _Ty();    // 調用構造函數
                    m_pShared->Incref();    // 構造函數拋出異常,這一行將不執行
                }
                void alloc()    // 假設malloc總能成功
                {
                    m_pShared 
            = static_cast<__SharedObject*>(malloc(sizeof(_Ty)+sizeof(__SharedObject)));
                    m_pShared
            ->m_nRef = 0;
                }

                _Ty 
            *get() const _THROW0() {   return (_Ty*)(m_pShared->Object());}
                __SharedObject
            * m_pShared;
            };

            可以寫代碼測試了,
                TShared_Ptr<int> pInt;
                pInt.Construct();
                (*pInt)++;
                TShared_Ptr<int> pInt1 = pInt;
                (*pInt1)++;
                咦,假如共享對象的構造函數帶有參數,咋辦呢?不要緊,重載多幾個Construct就行了,全部都是template 成員函數。由于要實現參數的完美轉發,又沒有C++2011的move之助,我還在堅持C++98ISO,要寫一大打呢,先示例幾個,很痛苦,或者,可通過宏來讓內心好受一點。
                template<typename T1>void Construct(const T1& t1);
                template<typename T1>void Construct(T1& t1);
                template<typename T1, typename T2>void Construct(const T1& t1, const T1& t2);
                template<typename T1, typename T2>void Construct(const T1& t1, T1& t2);
                template<typename T1, typename T2>void Construct(T1& t1, const T1& t2);
                template<typename T1, typename T2>void Construct(T1& t1, T1& t2);

                接下來就很清晰了,將shared_ptr的各種構造、賦值函數改寫一遍就是了。然后,就可以高高興興地測試使用了。以上,是最理想環境下TShared_Ptr的很簡單的實現,其操作接口多么的確簡明扼要。
                開始,考慮各種變態環境,其實也不變態,完全很正當的需求。各種也不多,就兩個而已:1、構造函數拋出異常,這個不是問題,由于TShared_Ptr的構造函數不拋出異常,其析構函數將被執行,檢查到計數器為0,所以不調用共享對象的析構函數;
                2、多繼承,這個有點頭痛了。先看看代碼,假設 class D : public B1, public B2,B1、B2都非空類;然后,B2* pB2 = pD,可以保證,(void*)pB2的值肯定不等于pD,也即是(void*)pB2 != (void*)pD。個中原因,在下就不多說了。但是,TShared_Ptr完全沒法模擬這種特性,假如,堅持這樣用,設pD為TShared_Ptr<D> ; 然后TShared_Ptr<B2>=pD,后果將不堪設想。一切皆因TShared_Ptr只有一條指向共享對象的指針,它還須擁有指向共享對象的基類子對象的指針,為此,必須添加此指向子對象的指針m_ptr,為_Ty*類型。因此,TShared_Ptr將內含兩個指針,大小就比普通指針大了一倍,無論如何,到此為止,不能讓它增大了。此外,TShared_Ptr增加了m_ptr成員后,還帶來一些別的好處,類型安全倒也罷了,關鍵是在VC中單步調試下,可清晰地看到共享對象的各種狀態,原來沒有m_ptr的時候,就只能悶聲發大財。
            template<typename _Ty>
            class TShared_Ptr
            {
            public:
                
                template
            <typename _OtherTy>
                TShared_Ptr(
            const TShared_Ptr<_OtherTy>& _Other)
                {    
                    m_ptr 
            = _Other.m_ptr;    // 類型安全全靠它了
                    m_pShared = const_cast<__SharedObject*>(_Other.m_pShared);
                    
            if (m_ptr != NULL)
                        m_pShared
            ->Incref();
                }
                
                __SharedObject
            * m_pShared;
                _Ty
            * m_ptr;
            };
                本輪子自然不美觀,使用也頗不方便,但勝在背地里做的勾當少,一切均在預料之中。好像多線程下,還有點問題,但那只是理論上的疑惑,實際運行中,該不會那么巧吧。
                咦,都什么年代,還在研究茴香豆的四種寫法,在下也承認,確實沒啥意義,但樂趣很無窮呢,我就是喜歡。珍惜生命,遠離C++。對了,想要完整的代碼嗎,沒那么容易

            posted @ 2012-10-30 15:39 華夏之火 閱讀(1723) | 評論 (5)編輯 收藏

            難以割舍的二段構造

                    兩段構造也是聲名狼藉得很,比之于MFC,好不了多少,貌似MFC中到處都是兩段構造,難道兩段構造的聲譽也是受MFC所累。定義完了一個對象變量之后,還要再調用一次該對象的Create函數,而且還要Create成功了之后,才能對該對象做進一步的操作,否則對象將一直處于非法狀態。這種代碼方式寫起來確實很惡心,為何不直接在構造函數中直接Create,不成功就拋出異常,然后對象就流產了,好過它半死不活地一直茍延殘喘于世上,累己累人。其實,MFC選擇兩段構造也是有苦衷:1、先是很久很久以前,VC編譯器對異常的支持不怎么好,當然,現在的VC編譯器,自然今時不比往日,但是,還要兼容以往的代碼;2、然后是MFC的設計,它只是對API做了一層薄薄的包裝,薄薄的意思,就是,不管怎么搗鼓,都難以將WINDOWS系統中的各種對象包裝成一個干凈的C++對象了,因為,API本身就采用兩段構造。可不是嗎?定義一個句柄變量,然后CreateXXX返回結果,返回值非法,表示創建失敗。失敗了,還要霸王硬上弓,后果會怎么樣,這誰也不知道。
                    理論上,構造函數拋出異常確實很優雅,代碼也更具美感,并且,其行為也更加明確,要么就處理,要么,就等著程序異常退出。但是,實際上,異常這種東西,真正實現執行起來,卻相當的困難。更何況,如果完全丟棄兩段法,除了異常,還會引入一些新的問題,正所謂:“前門驅虎,后門進狼”,進來不只是一只狼,而是好幾只。生活的奧妙,就在于制造出新的問題,以解決舊的問題。
                    構造函數中直接調用Create,就表示了用戶一定義一個類型變量,程序就會馬上啟動Create函數,也就意味著可能將創建窗口對象、內核對象、甚至啟動新的線程等等,這些操作都不是省油的燈,構造函數中做了太多事情,會有隱藏太多細節之嫌,代碼本來就是為了隱藏細節,這個多事之罪名暫且不論;但是,用戶沒法對創建過程Say NOT,也即是說,用戶一定義對象變量,就只能接受它的高昂的創建過程。難道,一開始就讓對象進入有效狀態,這都有錯嗎?確實是的。有時候,用戶只是先想聲明(定義)對象,等必要(時機成熟)的時候,再讓它進入有效狀態。咦,用戶這樣寫代碼,不太好吧,應該強制他/她等到了那個時候,再定義對象變量。變量怎么可以隨隨便便就定義呢?應該在要使用的時候,才定義它,這才是良好的代碼風格。但是,有些情況,確實需要先暫時定義非法狀態下的對象變量,比如,這個對象是另一個對象(擁有者)的成員變量時,那也沒什么,強制用戶在必要的時候,才定義擁有者對象變量。但是,假如這個擁有者必須是全局變量,那該怎么辦?那也沒什么,將擁有者定義為指針變量就是了?好了,本來只是要對象創建失敗的情況,現在還要考慮內存分配的細節,然后接著就是new delete,然后就是各種智能指針閃亮登臺演出,更糟糕的是,對象有效無效的問題依然沒有根除,因為,只要引入指針,每次使用指針,就必須檢查指針是否有效,咦,難道操作空指針不會拋出異常嗎?C++規范中,操作空指針屬后果未確定的行為,對C++而言,未確定往往就是最糟糕的意思。此外,鑒于對象只能一直處于有效狀態,它就不可能提供讓對象進入無效狀態的操作。如果想要讓對象無效,唯一的辦法,就是讓它死去,強制對象啟動析構函數,方法是離開作用域強者delete它。下次要使用它的時候,就再new一次或者定義一次,不,它已經是另外一條新生命了。但是,對于兩段構造的對象,只須Destroy又或者Create,對象可以永遠只有一個。此外,二段構造頗具擴展性,很輕易地就可搞成三段構造,每一步,用戶都有選擇的權利。但構造異常就沒有這個優點。
                    考慮到構造函數中的參數問題,比如,月份的參數,大家都知道,有效值只在1-12月之間。不討論這種情況下,非法的參數傳遞是否屬于代碼的邏輯問題。對此,構造異常指導下的對象是不可能出現無參(沒有參數或者參數都有缺省值)的構造函數,因此,它們也都不能用于數組,難以應用于全局變量、靜態變量、作為其他對象的數據成員,如果非要在這些場合下使用它們,比如占位符的作用,唯有用上指針,于是伴隨而來的,又如上文所述,使用指針之前,必須檢查指針的有效性,只怕不會比檢查二段構造的有效性好多少。
                    二段構造不輕易剝奪用戶的權利,提供更多選擇,可用于數組、堆棧、STL中的容器,要它死,它就死,要它活,它就活,但是,它可以從來都未曾消失過,要做的,僅僅是在使用它時,清楚它是死是活就行了,不過多加幾次判斷而已。相比之下,構造異常就更具侵入性了,一旦用上,就只能被迫遵照它的規則行事。
                    其實,兩段構造與構造異常,都很惡心,只要一處代碼中用到了它,所有與之相關的代碼都沒法脫身。差別不過在于誰比誰惡心而已,這個,視各人的口味而不同。對于本人這種害怕分配內存,釋放內存,更加畏懼異常的人來說(這并不表示本人寫不出異常安全的代碼),當然優先選擇二段構造,MORE EFFECTIVE的條款中,聲稱,如無必要,不要提供缺省的構造函數,以免對象陷入半死不活的狀態中。而我的習慣作法則是,如無必要,必須提供缺省的構造函數,不要輕易剝奪用戶想要使用對象數組的權利,或者是由于不提供缺省的構造函數,而由此引起的種種不便。
                    好了,既然程序中決定用二段構造了,那么,假如用戶定義了一個對象,忘了再構造一次,但是又要執行其他操作,怎么辦?嗯,那也沒什么,既然用戶不遵守契約,我們的對象自然可以做出種種不確定的行為。當然,別忘了,在其他的每一個操作上都添加幾條assert語句,盡管這很惡心,也聊勝于無,減少點罪惡感,以便于在調試版中找出問題。

            posted @ 2012-06-14 15:08 華夏之火 閱讀(3766) | 評論 (14)編輯 收藏

            C++沉思錄課堂練習另解--消息發送(優化版)

                 摘要:           緣起,看到一篇文章(懶得超鏈接),重新研究《C++沉思錄》的那一個課堂練習,綜合利用好幾種設計模式,并且很好地遵守結合的面向對象的原則,嗯,不管怎么樣,還是表揚這位同學的面向對象與設計模式的功力,確實不容易。只是,在下從模式堆里爬出來之后,已經對模式大倒胃口,看到這么小的練習,居然要用上這...  閱讀全文

            posted @ 2012-06-12 23:57 華夏之火 閱讀(2087) | 評論 (0)編輯 收藏

            僅列出標題
            共5頁: 1 2 3 4 5 

            導航

            統計

            常用鏈接

            留言簿(6)

            隨筆分類

            隨筆檔案

            搜索

            積分與排名

            最新評論

            閱讀排行榜

            評論排行榜

            国内精品欧美久久精品| 亚洲精品国产字幕久久不卡| 国产精品gz久久久| 免费一级欧美大片久久网| 久久热这里只有精品在线观看| 伊人久久大香线蕉AV色婷婷色| 久久久久久毛片免费播放| 精品99久久aaa一级毛片| 亚洲va久久久噜噜噜久久男同| 久久成人影院精品777| 亚洲一级Av无码毛片久久精品| 久久男人Av资源网站无码软件| 国内精品久久久久久久影视麻豆 | 久久精品国产亚洲AV高清热| 色综合久久综精品| 无码久久精品国产亚洲Av影片 | 7777久久久国产精品消防器材| 青青青国产成人久久111网站| 亚洲va久久久久| 国产精品久久网| 亚洲乱码精品久久久久..| 久久久WWW成人免费精品| 国产69精品久久久久777| 久久精品一区二区三区AV| 久久天天躁狠狠躁夜夜不卡| 四虎国产精品免费久久久| 久久久国产乱子伦精品作者| 亚洲va久久久噜噜噜久久狠狠| 久久笫一福利免费导航| 久久久久黑人强伦姧人妻| 四虎国产永久免费久久| 日本久久久精品中文字幕| 久久精品国产精品青草app| 99久久久精品免费观看国产| 亚洲国产欧美国产综合久久| 日产精品久久久久久久| 国产欧美久久久精品影院| 伊人精品久久久久7777| 久久一区二区三区免费| 国产精品久久新婚兰兰| 亚洲精品无码久久久久sm|