| 導購 | 订阅 | 在线投稿
分享
 
 
當前位置: 王朝網路 >> c/c++ >> 瘦身前後——兼談C++語言進化
 

瘦身前後——兼談C++語言進化

2008-06-01 02:10:58  編輯來源:互聯網  简体版  手機版  評論  字體: ||
 
 
  前一陣子寫了一篇文章,提到語言進化的職責之一,就是去除語言中的tricks(職責之二是去除非本質複雜性)。

  常看我blog的朋友肯定記得我曾寫過的boost源碼剖析系列。本來這個系列是打算成書的,但隨著對C++的熟悉發生了一些轉變,對語言級技術的熱衷逐漸消退,再回過頭來看boost庫中的一些組件,發現原本覺得很有寫的必要的東西頓時消失了。Scott Meyers的主頁上也列有一個寫Boost Under The Hood的計劃,一直也不見成文,興許也有類似的原因。

  一門語言應該是「Make simple things simple, make complex things possible」的。當我們用語言來表達思想的時候,這門語言應該能夠提供這樣的能力:即讓我們能夠最直接地表達我們的意思,多一分則太多,少一分則太少,好比古人形容美女:增一分則太肥,減一分則太瘦。

  這個問題上,有一個我認爲是廣泛的誤解,就是「KISS便意味著要精簡語言,並避免在編碼中使用『高階』語言特性」。對此有一句話我覺得說得好:你不能通過從一門語言中去掉東西來增加表達力。高階特性是一面利刃,用得不好固然傷了自己,但這並不表明就沒有用。任何東西都是在它真正適用的地方適用,霸王硬上弓的話弓斷弦崩反而傷及自身。所以,僅僅因爲高階特性輕易誤用(而且高階特性的確也輕易吸引人去用且輕易誤用,不過這是另一個問題),就斷然在任何地方都不用並宣稱這樣才是KISS的話,便因噎廢食了。舉個例子,高階函數是有用的,假如在真正需要高階函數的地方不用高階函數,那不是KISS,只能讓解決方案(或者更確切地說,workaround)更複雜。lambda函數是有用的,但假如在真正需要lambda的地方不使用lambda,也只能導致更複雜更不直觀的workarounds。OOP是有用的,但假如你的程序本來就只是簡單的「數據+操作」你偏要硬上OOP的話,不僅多了編碼時間,而且還降低程序的可見度和可維護性,後者就意味著項目的money。拿C++來說,這是一個廣爲诟病的問題。C++的偏向底層的應用領域決定了有不少地方使用C++其實就是「數據+操作」,然而很多人卻因爲用的是C++編譯器,便忍不住去使用高級特性,結果把本來簡單的事情複雜化——我自己就有不少次這樣的經曆:用了一大堆類之後,做完了回過頭來再看,這些類都幹嘛來著?需要嗎?最要害的就是要清楚自己做的是什麽事情,以及什麽工具才是對你所做的事情最適合的。

  說到這裏不妨順便說說另一個誤解:「假如我反正用不著C++裏面的高級特性,那還不如用C罷了」,鑒于C/C++的應用領域,的確有不少地方是可以用C++的C部分完成得很好的,所以這個誤解被傳播得還是蠻廣泛的。這裏的一個微妙的忽視在于:用C的話,你就用不到許多很好的C++庫了。用C++的話,你完全可以在你自己的編碼中不使用高階特性(說實話,這需要清醒的頭腦和豐富的經驗,以及克制能力),但你還是可以利用衆多的C++庫來簡化你的工作的:假如一個transform明明可以搞定的你偏要寫一個for出來難道能叫KISS?假如一個vector就能避免絕大多數內存治理漏洞和簡化內存治理工作你偏偏要手動malloc/free那能叫KISS(我見過不少用C++編碼卻到處都是malloc/free的)?假如最直接的方式是gc你偏偏要繞一大堆彎子才能保證正確釋放那也不叫KISS(等C++09吧)。假如一個for_each(readdir_sequence(".", readdir_sequence::files), ::remove);能搞定的你偏要寫:

  

  // in C

  DIR* dir = opendir(".");

  if(NULL != dir)

  {

  strUCt dirent* de;

  for(; NULL != (de = readdir(dir)); )

  {

  struct stat st;

  if( 0 == stat(de->d_name, &st) &&

  S_IFREG == (st.st_mode & S_IFMT))

  {

  remove(de->d_name);

  }

  }

  closedir(dir);

  }

  那能叫KISS?

  總之還是那句話:明確知道你想要表達的是什麽並用最簡潔(在不損害輕易理解性的前提下)的方式去表達它。但我認爲,最KISS不代表最原始。

  進化——兩個例子

  先舉一個平易近人的例子(Walter Bright——D語言發明者——曾在他的一個presentation中使用這個例子),假如我們想要遍曆一個數組,在C裏面我們是這麽做(或者用指針,不過指針有指針自己的問題):

  

  int arr[10];

  … // initialize arr

  for(int i = 0; i < 10; ++i)

  {

  int value = arr[i];

  …

  printf

  }

  這個貌似簡單的循環其實有幾個主要的問題:

  1. 下標索引不應該是int,而應該是size_t,int未必能足夠存放一個數組的下標。

  2. value的類型依靠于arr內元素的類型,違反DRY,假如arr的類型改變爲long或unsigned,就可能發生截斷。

  3. 這種for只能對數組工作,假如是另一個自定義容器就不行了。

  在現代C++裏面,則是這麽做:

  

  for(std::vector<int>::iterator

  iter = v.begin();

  iter != v.end();

  ++iter) {

  …

  }

  其實最大的問題就是一天三遍的寫,麻煩。for循環的這個問題上篇講auto的時候也提到。

  Walter Bright然後就把D裏面支持的foreach拿出來對比(當然,支持foreach的語言太多了,這也說明了這個結構的高效性)。

  

  foreach(i; v) {

  …

  }

  不多不少,剛好表達了意思:對v中的每個元素i做某某事情。

  這個例子有人說太Na?ve了,其實我也贊成,的確,天天不知道有多少程序員寫下一個個的循環結構,究竟有多少出了上面提到的三個問題呢?最大的問題恐怕還是數組越界。此外大家也都親身體驗過違反DRY原則的後果:改了一處地方的類型,編譯,發現到處都是類型錯誤,結果一通「查找——替換」是免不了的了,誰說程序員的時間是寶貴的來著?

  既然這個例子太Nave,那就說一個不那麽Nave的。Java爲什麽要加入closure?以C++STL爲例,假如我們要:

  transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), _1 + _2);

  也就是說將v1和v2裏面的元素對應相加然後放到v3當中去。這裏用了boost.lambda,但大家都知道boost.lambda又是一個經典的雞肋。_1 + _2還算湊活,一旦表達式複雜了,或者其中牽涉到對其它函數的調用了,簡直就是一場噩夢,比如說我們想把v1和v2中相應元素這樣相加:f(_1) + f(_2),其中f是一個函數或仿函數,可以做加權或者其它處理,那麽我們可以像下面這樣寫嗎:

  transform(…, f(_1) + f(_2));

  答案是不行,你得這樣寫:

  

  transform(…,

  boost::bind(std::plus<int>(), boost::bind(f, _1), boost::bind(f, _1))

  );

  Lisper們笑了,Haskeller們笑了,就連Javaer們都笑了。It』s not even funny! 這顯然違反了「simple things should be simple」原則。

  假如不想卷入C++ functional的噩夢的話,你也可以這麽寫:

  

  struct Op

  {

  int operator()(int a1, int a2) { return f(a1) + f(a2); }

  };

  transform(…, Op());

  稍微好一點,但這種做法也有很嚴重的問題。

  爲什麽Java加入closure,其實還是一個語法問題。從嚴格意義上,Java的anonymous class已經可以實現出一樣的功能了,正如C++的functor一樣。然而,代碼是給人看的,語言是給人用來寫代碼的,代碼的主要代價在維護,維護則需要閱讀、理解。寫代碼的人不希望多花筆墨來寫那些自己本不關心的東西,讀代碼的人也希望「所讀即所表」,不想看到代碼裏面有什麽彎子,最好是自然語言自然抽象才好呢。

  所以,盡管closure是一顆語法糖,但卻是一顆很甜很甜的糖,因爲有了closure你就可以寫:

  

  transform(…, <>(a1, a2){ f(a1) + f(a2) });

  Simple things should be simple!

  此外,closure最強大的好處還是在于對局部變量的方便的引用,設想我們想要創建的表達式是:

  

  int weight1 = 0.3, weight2 = 0.6;

  transform(…, f(_1)*weight1 + f(_2)*weight2);

  當然,上面的語句是非法的,不過使用closure便可以寫成:

  

  int weight1 = 0.3, weight2 = 0.6;

  transform(…, <&>(_1, _2){ f(_1)*weight1 + f(_2)*weight2 } );

  用functor class來實現同樣的功能則要麻煩許多,一旦麻煩,就會error-prone,一旦error-prone,就會消耗人力,而人力,就是金錢。

  C++09也有希望加入lambda,不過這是另一個話題,下回再說。

  The Real Deal——variadic templates

  C++的callback類,Google一下,沒有一打也有半打。其中尤數boost.function實現得最爲靈活周到。然而,就在其靈活周到的接口下面,卻是讓人不忍卒讀的實現;03年的時候我寫的第一篇boost源碼剖析就是boost.function的,當時還覺得能看懂那樣的代碼牛得不行...話說回來,那篇文章主要剖析了兩個方面,一個是它對不同參數的函數類型是如何處理的,第二個是一個type-erase設施。其中第一個方面就占去了大部分的篇幅。

  簡而言之,要實現一個泛型的callback類,就必須實現以下最常見的應用場景:

  

  function<int(int, int)> caller = f;

  int r = caller(1, 2); // call f

  爲此function類模板裏面肯定要有一個operator(),然而,接下來,如何定義這個operator()就成了問題:

  

  template<Signature>

  class function

  {

  operator()(???);

  };

  ???處填什麽?返回值處的???可以解決,用一個traits:typename result_type<Signature>::type,但參數列表處的???呢?

  boost采用的辦法也是C++98唯一的辦法,就是爲不同參數個數的Signature進行特化:

  

  template<typename R, typename T1>

  class function<R(T1)>

  {

  R operator()(T1 a1);

  };

  template<typename R, typename T1, typename T2>

  class function<R(T1, T2)>

  {

  R operator()(T1 a1, T2 a2);

  };

  template<typename R, typename T1, typename T2, typename T3>

  class function<R(T1, T2, T3)>

  {

  R operator()(T1 a1, T2 a2, T3 a3);

  };

  … // 再寫下去頁寬不夠了,打住…

  如此一共N(N由一個宏控制)個版本。

  這種做法有兩個問題:一,函數的參數個數始終還是受限的,你作出N個特化版本,那麽對N+1個參數的函數就沒轍了。boost::tuple也是這個問題。二,代碼重複。每個特化版本裏面除了參數個數不同之外基本其它都是相同的;boost解決這個問題的辦法是利用宏,宏本身的一大堆問題就不說了,你只要打開boost.function的主體實現代碼就知道有多糟糕了,近一千行代碼,其中涉及元編程和宏技巧無數,可讀性可以說基本爲0。好在這是個標准庫(boost.function將加入tr1)不用你維護,假如是你自己寫了用的庫,恐怕除了你誰也別想動了。所以第二個問題其實就是可讀性可維護性問題,用Matthew Wilson的說法就是可發現性和透明性的問題,這是一個很嚴重的問題,許多C++現代庫因爲這個問題而遭到诟病。

  現在,讓我們來看一看加入了variadic templates之後的C++09實現:

  

  template<typename R, typename... Args>

  struct invoker_base {

  virtual R invoke(Args...) = 0;

  virtual ~invoker_base() { }

  };

  template<typename F, typename R, typename... Args>

  struct functor_invoker : public invoker_base<R, Args...>

  {

  eXPlicit functor_invoker(F f) : f(f) { }

  R invoke(Args... args) { return f(args...); }

  private:

  F f;

  };

  template<typename Signature>

  class function;

  template<typename R, typename... Args>

  class function<R (Args...)>

  {

  public:

  template<typename F>

  function(F f) : invoker(0)

  {

  invoker = new functor_invoker<F, R, Args...>(f);

  }

  R operator()(Args... args) const

  {

  return invoker->invoke(args...);

  }

  private:

  invoker_base<R, Args...>* invoker;

  };

  整個核心實現就這些!一共才36行!加上析構函數拷貝構造函數等邊角料一共也就70行!更重要的是,整個代碼清楚無比,所有涉及到可變數目個模板參數的地方都由variadic templates代替。「Args…」恰如其分的表達了我們想要表達的意思——多個參數(數目不管)。與C++98的boost.function實現真是天壤之別!

  這裏function_invoker是用的type-erase手法,具體可參見我以前寫的boost.any源碼剖析,或上篇講auto的,或《C++ Template Metaprogramming》(內有元編程慎入!)。type-erase手法是像C++這樣的弱RTTI支持的語言中少數真正實用的手法,某種程度上設計模式裏面的adapter模式也是type-erase的一個變種。

  假如還覺得不夠的話,可以參考variadic-templates的主頁,上面的variadic templates proposal中帶了三個tr1實現,分別是tuple,bind,function,當然,variadic-templates的好處遠遠不僅僅止于這三個實現,從本質上它提供了一種真正直接的表達意圖的工具,完全避開了像下面這種horrible的workaround:

  

  template<class T1>

  cons(T1& t1, const null_type&, const null_type&, const null_type&,

  const null_type&, const null_type&, const null_type&,

  const null_type&, const null_type&, const null_type&)

  : head (t1) {}

  tuple的C++98實現,代碼近千行。利用variadic-templates實現,代碼僅百行。

  和這種更horrible的workaround:

  

  template<class R, class F, class A1, class A2, class A3, class A4, class A5, class A6>

  _bi::bind_t<R, F, typename _bi::list_av_6<A1, A2, A3, A4, A5, A6>::type>

  BOOST_BIND(boost::type<R>, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6)

  {

  typedef typename _bi::list_av_6<A1, A2, A3, A4, A5, A6>::type list_type;

  return _bi::bind_t<R, F, list_type>(f, list_type(a1, a2, a3, a4, a5, a6));

  }

  小小的boost.bind,實現代碼逾兩千行,其間重複代碼無數。用了variadic-templates,實現不過百行。

  BTW. variadic templates在C++大會上一次性幾乎全數投票通過。lambda能不能進標准則要看幾個提案者的工作。目前還沒有Wording出來。不過只要出了wording想必也會像variadic templates那樣壓倒性通過的。
 
 
 
上一篇《C++計算四則表達式的模板》
下一篇《C&C++論戰之C++真的還有未來嗎?》
 
 
 
 
 94.*.*.* 發表于2012-07-20 22:15:43
  At last, someone comes up with the "right" asnwer!
  回覆
  
  
 
 
 
日版寵物情人插曲《Winding Road》歌詞

日版寵物情人2017的插曲,很帶節奏感,日語的,女生唱的。 最後聽見是在第8集的時候女主手割傷了,然後男主用嘴幫她吸了一下,插曲就出來了。 歌手:Def...

兄弟共妻,我成了他們夜裏的美食

老鍾家的兩個兒子很特別,就是跟其他的人不太一樣,魔一般的執著。兄弟倆都到了要結婚的年齡了,不管自家老爹怎麽磨破嘴皮子,兄弟倆說不娶就不娶,老父母爲兄弟兩操碎了心...

如何磨出破洞牛仔褲?牛仔褲怎麽剪破洞?

把牛仔褲磨出有線的破洞 1、具體工具就是磨腳石,下面墊一個硬物,然後用磨腳石一直磨一直磨,到把那塊磨薄了,用手撕開就好了。出來的洞啊很自然的。需要貓須的話調幾...

我就是掃描下圖得到了敬業福和愛國福

先來看下敬業福和愛國福 今年春節,支付寶再次推出了“五福紅包”活動,表示要“把欠大家的敬業福都還給大家”。 今天該活動正式啓動,和去年一樣,需要收集“五福”...

冰箱異味産生的原因和臭味去除的方法

有時候我們打開冰箱就會聞到一股異味,冰箱裏的這種異味是因爲一些物質發出的氣味的混合體,聞起來讓人惡心。 産生這些異味的主要原因有以下幾點。 1、很多人有這種習...

《極品家丁》1-31集大結局分集劇情介紹

簡介 《極品家丁》講述了現代白領林晚榮無意回到古代金陵,並追隨蕭二小姐化名“林三”進入蕭府,不料卻陰差陽錯上演了一出低級家丁拼搏上位的“林三升職記”。...

李溪芮《極品家丁》片尾曲《你就是我最愛的寶寶》歌詞

你就是我最愛的寶寶 - 李溪芮 (電視劇《極品家丁》片尾曲) 作詞:常馨內 作曲:常馨內 你的眉 又鬼馬的挑 你的嘴 又壞壞的笑 上一秒吵鬧 下...

烏梅的功效與作用以及烏梅的食用禁忌有哪些?

烏梅,又稱春梅,中醫認爲,烏梅味酸,性溫,無毒,具有安心、除熱、下氣、祛痰、止渴調中、殺蟲的功效,治肢體痛、肺痨病。烏梅泡水喝能治傷寒煩熱、止吐瀉,與幹姜一起制...

什麽是脂肪粒?如何消除臉部脂肪粒?

什麽是脂肪粒 在我們的臉上總會長一個個像脂肪的小顆粒,弄也弄不掉,而且顔色還是白白的。它既不是粉刺也不是其他的任何痘痘,它就是脂肪粒。 脂肪粒雖然也是由油脂...

網絡安全治理:國家安全保障的主要方向是打擊犯罪,而不是處置和懲罰受害者

來源:中國青年報 新的攻擊方法不斷湧現,黑客幾乎永遠占據網絡攻擊的上風,我們不可能通過技術手段杜絕網絡攻擊。國家安全保障的主要方向是打擊犯罪,而不是處置和懲罰...

河南夫妻在溫嶺網絡直播“造人”內容涉黃被刑事拘留

夫妻網絡直播“造人”爆紅   1月9日,溫嶺城北派出所接到南京警方的協查通告,他們近期打掉了一個涉黃直播APP平台。而根據掌握的線索,其中有一對涉案的夫妻主播...

如何防止牆紙老化?牆紙變舊變黃怎麽辦?

如何防止牆紙老化? (1)選擇透氣性好的牆紙 市場上牆紙的材質分無紡布的、木纖維的、PVC的、玻璃纖維基材的、布面的等,相對而言,PVC材質的牆紙最不透氣...

鮮肌之謎非日本生産VS鮮肌之謎假日貨是謠言

觀點一:破日本銷售量的“鮮肌之謎” 非日本生産 近一段時間,淘寶上架了一款名爲“鮮肌之謎的” 鲑魚卵巢美容液,號稱是最近日本的一款推出的全新護膚品,産品本身所...

中國最美古詩詞精選摘抄

系腰裙(北宋詞人 張先) 惜霜蟾照夜雲天,朦胧影、畫勾闌。人情縱似長情月,算一年年。又能得、幾番圓。 欲寄西江題葉字,流不到、五亭前。東池始有荷新綠,尚小如...

關于女人的經典語句

關于女人的經典語句1、【做一個獨立的女人】 思想獨立:有主見、有自己的人生觀、價值觀。有上進心,永遠不放棄自己的理想,做一份自己喜愛的事業,擁有快樂和成就...

未來我們可以和性愛機器人結婚嗎?

你想體驗機器人性愛嗎?你想和性愛機器人結婚嗎?如果你想,機器人有拒絕你的權利嗎? 近日,第二屆“國際人類-機器人性愛研討會”大會在倫敦金史密斯大學落下帷幕。而...

全球最變態的十個地方

10.土耳其地下洞穴城市 變態指數:★★☆☆☆ 這是土耳其卡帕多西亞的一個著名景點,傳說是當年基督教徒們爲了躲避戰爭而在此修建。裏面曾住著20000人,...

科學家稱,人類死亡後意識將在另外一個宇宙中繼續存活

據英國《每日快報》報道,一位科學家兼理論家Robert Lanza博士宣稱,世界上並不存在人類死亡,死亡的只是身體。他認爲我們的意識借助我們體內的能量生存,而且...

《屏裏狐》片頭曲《我愛狐狸精》歌詞是什麽?

《我愛狐狸精》 - 劉馨棋   (電視劇《屏裏狐》主題曲)   作詞:金十三&李旦   作曲:劉嘉   狐狸精 狐狸仙   千年修...

 
 
 
  前一陣子寫了一篇文章,提到語言進化的職責之一,就是去除語言中的tricks(職責之二是去除非本質複雜性)。   常看我blog的朋友肯定記得我曾寫過的boost源碼剖析系列。本來這個系列是打算成書的,但隨著對C++的熟悉發生了一些轉變,對語言級技術的熱衷逐漸消退,再回過頭來看boost庫中的一些組件,發現原本覺得很有寫的必要的東西頓時消失了。Scott Meyers的主頁上也列有一個寫Boost Under The Hood的計劃,一直也不見成文,興許也有類似的原因。   一門語言應該是「Make simple things simple, make complex things possible」的。當我們用語言來表達思想的時候,這門語言應該能夠提供這樣的能力:即讓我們能夠最直接地表達我們的意思,多一分則太多,少一分則太少,好比古人形容美女:增一分則太肥,減一分則太瘦。   這個問題上,有一個我認爲是廣泛的誤解,就是「KISS便意味著要精簡語言,並避免在編碼中使用『高階』語言特性」。對此有一句話我覺得說得好:你不能通過從一門語言中去掉東西來增加表達力。高階特性是一面利刃,用得不好固然傷了自己,但這並不表明就沒有用。任何東西都是在它真正適用的地方適用,霸王硬上弓的話弓斷弦崩反而傷及自身。所以,僅僅因爲高階特性輕易誤用(而且高階特性的確也輕易吸引人去用且輕易誤用,不過這是另一個問題),就斷然在任何地方都不用並宣稱這樣才是KISS的話,便因噎廢食了。舉個例子,高階函數是有用的,假如在真正需要高階函數的地方不用高階函數,那不是KISS,只能讓解決方案(或者更確切地說,workaround)更複雜。lambda函數是有用的,但假如在真正需要lambda的地方不使用lambda,也只能導致更複雜更不直觀的workarounds。OOP是有用的,但假如你的程序本來就只是簡單的「數據+操作」你偏要硬上OOP的話,不僅多了編碼時間,而且還降低程序的可見度和可維護性,後者就意味著項目的money。拿C++來說,這是一個廣爲诟病的問題。C++的偏向底層的應用領域決定了有不少地方使用C++其實就是「數據+操作」,然而很多人卻因爲用的是C++編譯器,便忍不住去使用高級特性,結果把本來簡單的事情複雜化——我自己就有不少次這樣的經曆:用了一大堆類之後,做完了回過頭來再看,這些類都幹嘛來著?需要嗎?最要害的就是要清楚自己做的是什麽事情,以及什麽工具才是對你所做的事情最適合的。   說到這裏不妨順便說說另一個誤解:「假如我反正用不著C++裏面的高級特性,那還不如用C罷了」,鑒于C/C++的應用領域,的確有不少地方是可以用C++的C部分完成得很好的,所以這個誤解被傳播得還是蠻廣泛的。這裏的一個微妙的忽視在于:用C的話,你就用不到許多很好的C++庫了。用C++的話,你完全可以在你自己的編碼中不使用高階特性(說實話,這需要清醒的頭腦和豐富的經驗,以及克制能力),但你還是可以利用衆多的C++庫來簡化你的工作的:假如一個transform明明可以搞定的你偏要寫一個for出來難道能叫KISS?假如一個vector就能避免絕大多數內存治理漏洞和簡化內存治理工作你偏偏要手動malloc/free那能叫KISS(我見過不少用C++編碼卻到處都是malloc/free的)?假如最直接的方式是gc你偏偏要繞一大堆彎子才能保證正確釋放那也不叫KISS(等C++09吧)。假如一個for_each(readdir_sequence(".", readdir_sequence::files), ::remove);能搞定的你偏要寫: // in C DIR* dir = opendir("."); if(NULL != dir) { strUCt dirent* de; for(; NULL != (de = readdir(dir)); ) { struct stat st; if( 0 == stat(de->d_name, &st) && S_IFREG == (st.st_mode & S_IFMT)) { remove(de->d_name); } }  closedir(dir); }   那能叫KISS?   總之還是那句話:明確知道你想要表達的是什麽並用最簡潔(在不損害輕易理解性的前提下)的方式去表達它。但我認爲,最KISS不代表最原始。   進化——兩個例子   先舉一個平易近人的例子(Walter Bright——D語言發明者——曾在他的一個presentation中使用這個例子),假如我們想要遍曆一個數組,在C裏面我們是這麽做(或者用指針,不過指針有指針自己的問題): int arr[10]; … // initialize arr for(int i = 0; i < 10; ++i) { int value = arr[i]; … printf }   這個貌似簡單的循環其實有幾個主要的問題:   1. 下標索引不應該是int,而應該是size_t,int未必能足夠存放一個數組的下標。   2. value的類型依靠于arr內元素的類型,違反DRY,假如arr的類型改變爲long或unsigned,就可能發生截斷。   3. 這種for只能對數組工作,假如是另一個自定義容器就不行了。   在現代C++裏面,則是這麽做: for(std::vector<int>::iterator iter = v.begin(); iter != v.end(); ++iter) { … }   其實最大的問題就是一天三遍的寫,麻煩。for循環的這個問題上篇講auto的時候也提到。   Walter Bright然後就把D裏面支持的foreach拿出來對比(當然,支持foreach的語言太多了,這也說明了這個結構的高效性)。 foreach(i; v) { … }   不多不少,剛好表達了意思:對v中的每個元素i做某某事情。   這個例子有人說太Na?ve了,其實我也贊成,的確,天天不知道有多少程序員寫下一個個的循環結構,究竟有多少出了上面提到的三個問題呢?最大的問題恐怕還是數組越界。此外大家也都親身體驗過違反DRY原則的後果:改了一處地方的類型,編譯,發現到處都是類型錯誤,結果一通「查找——替換」是免不了的了,誰說程序員的時間是寶貴的來著?   既然這個例子太Nave,那就說一個不那麽Nave的。Java爲什麽要加入closure?以C++STL爲例,假如我們要: transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), _1 + _2);   也就是說將v1和v2裏面的元素對應相加然後放到v3當中去。這裏用了boost.lambda,但大家都知道boost.lambda又是一個經典的雞肋。_1 + _2還算湊活,一旦表達式複雜了,或者其中牽涉到對其它函數的調用了,簡直就是一場噩夢,比如說我們想把v1和v2中相應元素這樣相加:f(_1) + f(_2),其中f是一個函數或仿函數,可以做加權或者其它處理,那麽我們可以像下面這樣寫嗎: transform(…, f(_1) + f(_2));   答案是不行,你得這樣寫: transform(…, boost::bind(std::plus<int>(), boost::bind(f, _1), boost::bind(f, _1)) );   Lisper們笑了,Haskeller們笑了,就連Javaer們都笑了。It』s not even funny! 這顯然違反了「simple things should be simple」原則。   假如不想卷入C++ functional的噩夢的話,你也可以這麽寫: struct Op { int operator()(int a1, int a2) { return f(a1) + f(a2); } }; transform(…, Op());   稍微好一點,但這種做法也有很嚴重的問題。   爲什麽Java加入closure,其實還是一個語法問題。從嚴格意義上,Java的anonymous class已經可以實現出一樣的功能了,正如C++的functor一樣。然而,代碼是給人看的,語言是給人用來寫代碼的,代碼的主要代價在維護,維護則需要閱讀、理解。寫代碼的人不希望多花筆墨來寫那些自己本不關心的東西,讀代碼的人也希望「所讀即所表」,不想看到代碼裏面有什麽彎子,最好是自然語言自然抽象才好呢。   所以,盡管closure是一顆語法糖,但卻是一顆很甜很甜的糖,因爲有了closure你就可以寫: transform(…, <>(a1, a2){ f(a1) + f(a2) }); Simple things should be simple!   此外,closure最強大的好處還是在于對局部變量的方便的引用,設想我們想要創建的表達式是: int weight1 = 0.3, weight2 = 0.6; transform(…, f(_1)*weight1 + f(_2)*weight2);   當然,上面的語句是非法的,不過使用closure便可以寫成: int weight1 = 0.3, weight2 = 0.6; transform(…, <&>(_1, _2){ f(_1)*weight1 + f(_2)*weight2 } );   用functor class來實現同樣的功能則要麻煩許多,一旦麻煩,就會error-prone,一旦error-prone,就會消耗人力,而人力,就是金錢。   C++09也有希望加入lambda,不過這是另一個話題,下回再說。 The Real Deal——variadic templates   C++的callback類,Google一下,沒有一打也有半打。其中尤數boost.function實現得最爲靈活周到。然而,就在其靈活周到的接口下面,卻是讓人不忍卒讀的實現;03年的時候我寫的第一篇boost源碼剖析就是boost.function的,當時還覺得能看懂那樣的代碼牛得不行...話說回來,那篇文章主要剖析了兩個方面,一個是它對不同參數的函數類型是如何處理的,第二個是一個type-erase設施。其中第一個方面就占去了大部分的篇幅。   簡而言之,要實現一個泛型的callback類,就必須實現以下最常見的應用場景: function<int(int, int)> caller = f; int r = caller(1, 2); // call f   爲此function類模板裏面肯定要有一個operator(),然而,接下來,如何定義這個operator()就成了問題: template<Signature> class function { operator()(???); };   ???處填什麽?返回值處的???可以解決,用一個traits:typename result_type<Signature>::type,但參數列表處的???呢?   boost采用的辦法也是C++98唯一的辦法,就是爲不同參數個數的Signature進行特化: template<typename R, typename T1> class function<R(T1)> { R operator()(T1 a1); }; template<typename R, typename T1, typename T2> class function<R(T1, T2)> { R operator()(T1 a1, T2 a2); }; template<typename R, typename T1, typename T2, typename T3> class function<R(T1, T2, T3)> { R operator()(T1 a1, T2 a2, T3 a3); }; … // 再寫下去頁寬不夠了,打住…   如此一共N(N由一個宏控制)個版本。   這種做法有兩個問題:一,函數的參數個數始終還是受限的,你作出N個特化版本,那麽對N+1個參數的函數就沒轍了。boost::tuple也是這個問題。二,代碼重複。每個特化版本裏面除了參數個數不同之外基本其它都是相同的;boost解決這個問題的辦法是利用宏,宏本身的一大堆問題就不說了,你只要打開boost.function的主體實現代碼就知道有多糟糕了,近一千行代碼,其中涉及元編程和宏技巧無數,可讀性可以說基本爲0。好在這是個標准庫(boost.function將加入tr1)不用你維護,假如是你自己寫了用的庫,恐怕除了你誰也別想動了。所以第二個問題其實就是可讀性可維護性問題,用Matthew Wilson的說法就是可發現性和透明性的問題,這是一個很嚴重的問題,許多C++現代庫因爲這個問題而遭到诟病。   現在,讓我們來看一看加入了variadic templates之後的C++09實現: template<typename R, typename... Args> struct invoker_base { virtual R invoke(Args...) = 0; virtual ~invoker_base() { } }; template<typename F, typename R, typename... Args> struct functor_invoker : public invoker_base<R, Args...> { eXPlicit functor_invoker(F f) : f(f) { } R invoke(Args... args) { return f(args...); } private: F f; }; template<typename Signature> class function; template<typename R, typename... Args> class function<R (Args...)> { public: template<typename F> function(F f) : invoker(0) { invoker = new functor_invoker<F, R, Args...>(f); } R operator()(Args... args) const { return invoker->invoke(args...); } private: invoker_base<R, Args...>* invoker; };   整個核心實現就這些!一共才36行!加上析構函數拷貝構造函數等邊角料一共也就70行!更重要的是,整個代碼清楚無比,所有涉及到可變數目個模板參數的地方都由variadic templates代替。「Args…」恰如其分的表達了我們想要表達的意思——多個參數(數目不管)。與C++98的boost.function實現真是天壤之別!   這裏function_invoker是用的type-erase手法,具體可參見我以前寫的boost.any源碼剖析,或上篇講auto的,或《C++ Template Metaprogramming》(內有元編程慎入!)。type-erase手法是像C++這樣的弱RTTI支持的語言中少數真正實用的手法,某種程度上設計模式裏面的adapter模式也是type-erase的一個變種。   假如還覺得不夠的話,可以參考variadic-templates的主頁,上面的variadic templates proposal中帶了三個tr1實現,分別是tuple,bind,function,當然,variadic-templates的好處遠遠不僅僅止于這三個實現,從本質上它提供了一種真正直接的表達意圖的工具,完全避開了像下面這種horrible的workaround: template<class T1> cons(T1& t1, const null_type&, const null_type&, const null_type&, const null_type&, const null_type&, const null_type&, const null_type&, const null_type&, const null_type&) : head (t1) {}   tuple的C++98實現,代碼近千行。利用variadic-templates實現,代碼僅百行。   和這種更horrible的workaround: template<class R, class F, class A1, class A2, class A3, class A4, class A5, class A6> _bi::bind_t<R, F, typename _bi::list_av_6<A1, A2, A3, A4, A5, A6>::type> BOOST_BIND(boost::type<R>, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) { typedef typename _bi::list_av_6<A1, A2, A3, A4, A5, A6>::type list_type; return _bi::bind_t<R, F, list_type>(f, list_type(a1, a2, a3, a4, a5, a6)); }   小小的boost.bind,實現代碼逾兩千行,其間重複代碼無數。用了variadic-templates,實現不過百行。   BTW. variadic templates在C++大會上一次性幾乎全數投票通過。lambda能不能進標准則要看幾個提案者的工作。目前還沒有Wording出來。不過只要出了wording想必也會像variadic templates那樣壓倒性通過的。
󰈣󰈤
 
 
 
  免責聲明:本文僅代表作者個人觀點,與王朝網路無關。王朝網路登載此文出於傳遞更多信息之目的,並不意味著贊同其觀點或證實其描述,其原創性以及文中陳述文字和內容未經本站證實,對本文以及其中全部或者部分內容、文字的真實性、完整性、及時性本站不作任何保證或承諾,請讀者僅作參考,並請自行核實相關內容。
 
 
高清美女攝影(8)
高清美女攝影(7)
高清美女攝影(6)
高清美女攝影(5)
痞子的甘南日記
疑是銀河落九天
雪域壩上四——純美色
冬日戀歌——西城楊柳弄輕柔
 
>>返回首頁<<
 
 熱帖排行
 
 
 
 
© 2005- 王朝網路 版權所有