青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

huaxiazhihuo

 

stl中string的一種改造

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

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

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

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

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

C語言復(fù)雜聲明的本質(zhì)與局限

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

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

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

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

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

鍵盤布局的改進之道

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

一點反省

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

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

非理性擁護C++

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

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

略說成員函數(shù)指針及其模板的精簡實現(xiàn)

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

輕量級共享對象的靈巧指針的實現(xiàn)

    毫無疑問,shared_ptr的功能不可謂不強大,設(shè)計不可謂不精巧,它的抽象級別不是一般的高,不僅要管理一般的C++內(nèi)存資源,更染指其他的非C++資源,比如文件、比如連接、……,只要給它一個支點(釋放資源的函數(shù)),不僅如此,還能頑強地生存于各種惡劣的環(huán)境,好比多線程、引用循環(huán)。當(dāng)然,代價是有的,它背地里做了很多不為人知的勾當(dāng),表面上僅僅一行的帶有構(gòu)造函數(shù)shared_ptr的定義代碼,編譯器卻要很無奈地生成一個莫明其妙的多態(tài)模板類(_Ref_count_base的繼承類,帶有虛函數(shù)表,意味著不能內(nèi)聯(lián),用以在恰當(dāng)?shù)臅r機,釋放資源),更別提要多了一堆指令,當(dāng)然,在當(dāng)今硬件性能蓬勃發(fā)展的美好時代,這點代價根本就不算什么,比之于那些什么所謂的虛擬機,甚至可以忽略不計。但是,總是有那么一批老古董,總會強迫假想自己寫的程序會運行于各種資源非常苛刻的環(huán)境下,內(nèi)心就是沒法原諒shared_ptr所帶來的極細微的損失。好比區(qū)區(qū)在下,每一次一用到shared_ptr,心里的那種負罪感啊,又多了幾條廢指令,又浪費多了十幾個的堆字節(jié),是否將生成內(nèi)存碎片啊。終于有一刻頂不住了啦,去你媽的shared_ptr,老子不過想讓你老老實實的代理內(nèi)存資源,本本分分地做好你的分內(nèi)之事,不勞你費心照顧其他的系統(tǒng)資源對象,那些場合本座自然有更好的解決方式。于是,制造輪子的悲劇又再次誕生了,雖然,他們一直在內(nèi)心深處抵制新輪子的愚蠢行為,但是,……,只能說,知我者謂我心憂,不知我者謂我何求。
    每次想到shared_ptr要new一個_Ref_count_base的對象來管理計數(shù),有人就恨得牙根發(fā)癢,巴不得把_Ref_count_base的數(shù)據(jù)成員搬過來,放之于那個要管理的對象的身上,以減少一小塊內(nèi)存。假如,客戶傳到shared_ptr構(gòu)造函數(shù)的指針,此指針?biāo)傅膬?nèi)存,能再多幾個字節(jié)(一個字節(jié)也行,最大值255,已足矣),以供我等存放一個long型的計數(shù)器,那就好辦了。白癡也知道,這是不可能的事情。除非,此對象由shared_ptr來構(gòu)造,那么還有辦法再放多點額外內(nèi)存進去。此話怎講?大家都知道,C++中, new一個對象時,即意味著兩步操作:1、分配一塊內(nèi)存;2、在此塊內(nèi)存上執(zhí)行對象的構(gòu)造函數(shù)。如果第1步的分配內(nèi)存,能作多點手腳,比如說,分配一塊比對象本身所占空間還要大的內(nèi)存,那么我們的shared_ptr就可以把計數(shù)器放進對象之中了,也無須再new一個新的_Ref_count_base對象來管理計數(shù)器了。兩塊內(nèi)存,合二為一,雙劍合璧,妙哉妙哉。但,這如何做到呢?
    以下,是一個類從簡單到復(fù)雜的物種進化歷程。C++中,只要是通用類,即使再簡單的需求,要寫得可以被普遍承認,可以高高興興地到處使用,都絕非易事。而且,更悲劇的是,辛辛苦苦,嘔心瀝血造出來的輪子,還很有可能一問世就直接被槍斃,就算能茍且活下來,也不會有車愿意組裝這一個廢輪子。
    廢話不說,書接上文,很明顯,對象的new操作應(yīng)該由我們的shared_ptr來掌控。任由用戶來new,就太遲了,對象的內(nèi)存塊已經(jīng)確定下來了,沒文章可做啦。換句話說,shared_ptr必須模擬標(biāo)準(zhǔn)的new的兩在操作分配內(nèi)存和調(diào)用構(gòu)造函數(shù)。由此可知,以下所探討的shared_ptr運用場合也很有限,只適合于那些能看到構(gòu)造函數(shù)并且知道其大小的C++類,所以,請大伙兒不要抱怨。唯其需求簡單明確,所以才能高效。
首先,用一結(jié)構(gòu)體__SharedObject來包含計數(shù)器和對象,如下所示:
struct __SharedObject
{
    
void* Object()    // 返回對象的地址,由于不知對象的類型,所以只能是void*,表示內(nèi)存地址
    { return this+1; }
   
long Incref() { return InterlockedIncrement(&m_nRef); }
    
long Decref() { return InterlockedDecrement(&m_nRef); }
   
long m_nRef;
};
    是否很簡陋,本座沒法也不想把它整得更加好看了。
    我們的shared_ptr,就暫時叫TShared_Ptr好了,其包含的數(shù)據(jù)成員,暫時很簡單。就只有一個__SharedObject的指針而已,后面由于多繼承多態(tài)的原因,將被迫多增加一個指針。
    好了,先看看TShared_Ptr的使用之道,此乃class template。由于共享對象由TShared_Ptr所造,所以,在其誕生之前,首先勢必定義一TShared_Ptr變量,好比,TShared_Ptr<int> pInt;考慮TShared_Ptr的構(gòu)造函數(shù),如果在里面就急急忙忙給共享對象分配內(nèi)存,將沒法表達空指針這個概念,所以它的無參構(gòu)造函數(shù)只是簡單地將m_pShared置為NULL。然后,TShared_Ptr必須提供分配內(nèi)存并執(zhí)行構(gòu)造函數(shù)的操作,叫Construct吧;然后,析構(gòu)函數(shù)也絕不含糊,執(zhí)行對象的析構(gòu)函數(shù)并釋放內(nèi)存。于是,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();    // 調(diào)用構(gòu)造函數(shù)
        m_pShared->Incref();    // 構(gòu)造函數(shù)拋出異常,這一行將不執(zhí)行
    }
    void alloc()    // 假設(shè)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)++;
    咦,假如共享對象的構(gòu)造函數(shù)帶有參數(shù),咋辦呢?不要緊,重載多幾個Construct就行了,全部都是template 成員函數(shù)。由于要實現(xiàn)參數(shù)的完美轉(zhuǎn)發(fā),又沒有C++2011的move之助,我還在堅持C++98ISO,要寫一大打呢,先示例幾個,很痛苦,或者,可通過宏來讓內(nèi)心好受一點。
    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的各種構(gòu)造、賦值函數(shù)改寫一遍就是了。然后,就可以高高興興地測試使用了。以上,是最理想環(huán)境下TShared_Ptr的很簡單的實現(xiàn),其操作接口多么的確簡明扼要。
    開始,考慮各種變態(tài)環(huán)境,其實也不變態(tài),完全很正當(dāng)?shù)男枨?。各種也不多,就兩個而已:1、構(gòu)造函數(shù)拋出異常,這個不是問題,由于TShared_Ptr的構(gòu)造函數(shù)不拋出異常,其析構(gòu)函數(shù)將被執(zhí)行,檢查到計數(shù)器為0,所以不調(diào)用共享對象的析構(gòu)函數(shù);
    2、多繼承,這個有點頭痛了。先看看代碼,假設(shè) class D : public B1, public B2,B1、B2都非空類;然后,B2* pB2 = pD,可以保證,(void*)pB2的值肯定不等于pD,也即是(void*)pB2 != (void*)pD。個中原因,在下就不多說了。但是,TShared_Ptr完全沒法模擬這種特性,假如,堅持這樣用,設(shè)pD為TShared_Ptr<D> ; 然后TShared_Ptr<B2>=pD,后果將不堪設(shè)想。一切皆因TShared_Ptr只有一條指向共享對象的指針,它還須擁有指向共享對象的基類子對象的指針,為此,必須添加此指向子對象的指針m_ptr,為_Ty*類型。因此,TShared_Ptr將內(nèi)含兩個指針,大小就比普通指針大了一倍,無論如何,到此為止,不能讓它增大了。此外,TShared_Ptr增加了m_ptr成員后,還帶來一些別的好處,類型安全倒也罷了,關(guān)鍵是在VC中單步調(diào)試下,可清晰地看到共享對象的各種狀態(tài),原來沒有m_ptr的時候,就只能悶聲發(fā)大財。
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;
};
    本輪子自然不美觀,使用也頗不方便,但勝在背地里做的勾當(dāng)少,一切均在預(yù)料之中。好像多線程下,還有點問題,但那只是理論上的疑惑,實際運行中,該不會那么巧吧。
    咦,都什么年代,還在研究茴香豆的四種寫法,在下也承認,確實沒啥意義,但樂趣很無窮呢,我就是喜歡。珍惜生命,遠離C++。對了,想要完整的代碼嗎,沒那么容易

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

難以割舍的二段構(gòu)造

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

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

C++沉思錄課堂練習(xí)另解--消息發(fā)送(優(yōu)化版)

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

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

僅列出標(biāo)題
共5頁: 1 2 3 4 5 

導(dǎo)航

統(tǒng)計

常用鏈接

留言簿(6)

隨筆分類

隨筆檔案

搜索

積分與排名

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            亚洲一区一卡| 亚洲国产精品一区| 欧美国产日本韩| 欧美在线观看视频一区二区三区 | 国产曰批免费观看久久久| 欧美福利在线观看| 欧美在线观看天堂一区二区三区| 日韩视频在线观看| 亚洲福利国产精品| 久久久久久91香蕉国产| 亚洲综合三区| 亚洲性xxxx| 在线亚洲自拍| 一区二区日韩伦理片| 午夜在线一区| 在线亚洲成人| 一区二区三区精品在线| 亚洲看片一区| 亚洲人成在线观看一区二区| 欧美成人一区二区三区| 久久在线视频| 久久久亚洲午夜电影| 欧美专区中文字幕| 香蕉尹人综合在线观看| 亚洲欧美日韩另类| 欧美一区二区三区久久精品茉莉花| 在线中文字幕不卡| 亚洲一区成人| 亚洲综合二区| 篠田优中文在线播放第一区| 亚洲免费视频观看| 亚洲综合色在线| 香蕉久久夜色精品国产使用方法| 亚洲一区网站| 欧美一区二区成人6969| 香蕉成人啪国产精品视频综合网| 午夜在线观看免费一区| 欧美制服丝袜第一页| 久久精品国产999大香线蕉| 欧美亚洲视频一区二区| 久久国产精品72免费观看| 久久久久国产精品一区三寸| 久久综合中文| 欧美好骚综合网| 亚洲一区亚洲二区| 香蕉久久国产| 玖玖视频精品| 亚洲二区在线观看| 日韩亚洲综合在线| 亚洲一区二区在线看| 亚洲欧洲视频在线| 亚洲精选在线观看| 亚洲欧美日本另类| 久久夜色精品亚洲噜噜国产mv | 国产精品国产成人国产三级| 国产精品国产三级国产a| 国产精品综合久久久| 国产视频自拍一区| 亚洲国产精品久久久久秋霞蜜臀| 亚洲人成人一区二区在线观看| 一区二区电影免费在线观看| 午夜国产精品视频| 蜜桃伊人久久| 日韩一区二区免费高清| 亚洲欧美国产精品va在线观看| 久久精品久久综合| 欧美精品久久久久久久| 国产精品亚洲成人| 亚洲国产精品一区二区三区| 亚洲少妇一区| 久久一本综合频道| 99国产精品久久久久久久久久| 午夜精彩国产免费不卡不顿大片| 免费毛片一区二区三区久久久| 欧美亚男人的天堂| 在线日韩视频| 亚洲欧美日韩视频一区| 欧美风情在线| 亚洲欧美日韩一区在线观看| 免费美女久久99| 国产麻豆视频精品| 亚洲精品综合精品自拍| 久久久久久久性| 99精品国产高清一区二区| 久久久久www| 国产精品久久7| 亚洲国产视频一区| 久久成人资源| 亚洲精品美女免费| 久久精品中文字幕一区| 国产精品v欧美精品v日韩| 在线观看久久av| 久久aⅴ乱码一区二区三区| 亚洲麻豆av| 久久亚洲高清| 国产在线观看一区| 亚洲欧美激情四射在线日 | 午夜精品福利视频| 欧美日韩国产91| 亚洲国产黄色片| 久久久91精品国产一区二区三区| 日韩视频永久免费| 欧美激情视频网站| 伊人久久婷婷色综合98网| 久久国产精品99国产| 亚洲无线视频| 欧美日韩一区免费| 亚洲三级免费| 欧美成人一区二区三区片免费| 午夜影院日韩| 国产麻豆日韩欧美久久| 午夜精品视频在线观看| 日韩午夜电影av| 欧美日韩99| 99在线热播精品免费| 欧美国产精品人人做人人爱| 久久成人综合网| 国产视频一区二区在线观看 | 亚洲第一精品电影| 久久综合国产精品| 在线观看视频免费一区二区三区| 久久久精品一区二区三区| 亚洲欧美综合精品久久成人| 国产精品毛片va一区二区三区 | 日韩午夜剧场| 最新中文字幕亚洲| 欧美精品导航| 亚洲色无码播放| 在线午夜精品| 国产精品影院在线观看| 欧美一级淫片aaaaaaa视频| 亚洲一区欧美二区| 国产欧美日韩视频一区二区三区| 欧美在线高清视频| 欧美在线不卡| 一区二区三区在线观看视频| 欧美成年人视频网站| 模特精品在线| 99综合电影在线视频| 一本到高清视频免费精品| 国产精品福利在线观看| 欧美一区国产在线| 久久精品国产成人| 亚洲国产精品成人一区二区| 亚洲国产欧美在线| 欧美日韩和欧美的一区二区| 亚洲一区二区三区影院| 亚洲一区三区电影在线观看| 国产综合视频| 亚洲高清不卡| 欧美视频观看一区| 久久久久久久综合日本| 免费成人黄色片| 亚洲图片激情小说| 欧美亚洲免费| 亚洲区一区二| 亚洲视频一二| 影音先锋成人资源站| 亚洲人成网站777色婷婷| 国产精品乱人伦中文| 免费日韩av片| 欧美天天在线| 欧美1区视频| 欧美亚州一区二区三区| 久久久久久久久久久久久9999| 狂野欧美性猛交xxxx巴西| 在线视频欧美一区| 欧美在线看片a免费观看| 亚洲精品字幕| 欧美亚洲自偷自偷| 99国产精品国产精品久久| 性欧美在线看片a免费观看| 亚洲精品乱码久久久久久蜜桃麻豆| 亚洲午夜精品久久久久久浪潮 | 日韩视频―中文字幕| 国产亚洲激情在线| 91久久精品一区二区别| 国产亚洲福利社区一区| 亚洲高清在线播放| 国产区二精品视| 91久久中文字幕| 一区二区三区在线视频观看| 亚洲最新在线| 亚洲国产精品123| 亚洲欧美日韩直播| 一区二区三区四区国产| 久久久999国产| 亚洲欧洲av一区二区| 欧美久久一级| 久久香蕉精品| 国产日韩欧美在线视频观看| 日韩一区二区精品视频| 亚洲高清视频一区二区| 性色av一区二区三区在线观看 | 久久精品99| 亚洲欧美伊人| 欧美日韩精品系列| 欧美激情中文字幕一区二区| 国色天香一区二区| 亚洲欧美大片|