1. 程式人生 > >《C++0x漫談》系列之:Concept, Concept!

《C++0x漫談》系列之:Concept, Concept!

C++0x漫談》系列之:Concept, Concept!

By 劉未鵬(pongba)

C++0x漫談》系列導言

這個系列其實早就想寫了,斷斷續續關注C++0x也大約有兩年餘了,其間看著各個重要proposals一路review過來:rvalue-referencesconceptsmemory-modelvariadic-templatestemplate-aliasesauto/decltypeGCinitializer-lists…

總的來說C++09C++98相比的變化是極其重大的。這個變化體現在三個方面,一個是形式上的變化,即在編碼形式層面的支援,也就是對應我們所謂的程式設計正規化

(paradigm)C++09不會引入新的程式設計正規化,但在對泛型程式設計(GP)這個正規化的支援上會得到質的提高:conceptsvariadic-templatesauto/decltypetemplate-aliasesinitializer-lists皆屬於這類特性。另一個是內在的變化,即並非程式碼組織表達方面的,memory-modelGC屬於這一類。最後一個是既有形式又有內在的,r-value references屬於這類。

這個系列如果能夠寫下去,會陸續將C++09的新特性介紹出來。鑑於已經有許多牛人寫了很多很好的tutor這裡這裡,還有C++標準主頁上的一些introductive

proposals,如這裡,此外C++社群中老當益壯的Lawrence Crowl也在google做了)。所以我就不作重複勞動了:),我會盡量從一個巨集觀的層面,如特性引入的動機,特性引入過程中經歷的修改,特性本身的最具代表性的使用場景,特性對程式設計正規化的影響等方面進行介紹。至於細節,大家可以見每篇介紹末尾的延伸閱讀。

Concept

好吧好吧,我承認我跳票了,上次說這次要寫variadic templates的。但g9老大寫了,讓我覺得concept應該先寫,因為這實在是個有意思的特性,比variadic templates有意思多了。

我和Concept不得不說的事

事兒#1

看看下面這坨程式碼有什麼問題:

std::list<int> li;

std::sort(li.begin(), li.end());

如果對人肉編譯不在行的話,可以用你手頭的編譯器試一下。你會發現,你的編譯器一碰到這簡單而無辜的兩行程式碼便會一反常態,跟個長舌婦似的吐出一大堆&#$@*^,令人牙酸的錯誤資訊來。在使用C++模板庫時這種編譯錯誤井噴是家常便飯,。你還以為不是編譯器井噴,而是你自己RP井噴了,於是一臉無辜地跑去問模板達人,後者擡了擡眼皮,告訴你說“把list改成vector因為listiterator不是random的而std::sort需要randomiterator”,你一邊在腦子裡給這句話分詞加標點符號一邊想弄明白他是怎麼從一堆毛線似的字元裡抽象出這麼個結論的。

實際上,這個問題比你想像得嚴重,其根本問題在於降低工作效率,你得在你本不需要花工夫的地方(人肉解析編譯錯誤)花工夫;這個問題比你想像得普遍,乃至於居然有人把“能夠獨立地解決所有的編譯與連結問題”也列在了“有實際開發工作經驗”要求裡面;這個問題比你想像得影響惡劣,因為你可以想像可憐的新手在兩行貌似無辜的程式碼面前哭喪臉的模樣——C++編譯器就這樣把一個可憐的潛在C++使用者給扼殺了。你也可以想像為什麼有那麼多人不喜歡C++模板——其實語法只是其一個非主要的方面。

實際上你請教的那個達人並沒有什麼火星抽象能力,只不過是吃過的橋比你走過的鹽還多而已。而這,還預示著另一個問題,就是能人肉解析模板編譯錯誤居然也成為了衡量C++達人與否的一個標準不信你去各個罈子上轉一轉看看有多少帖子是詢問關於編譯錯誤的問題的,其中又有多少是關於模板編譯錯誤的。

更小概率的是居然還存在一個專門解析STL相關錯誤資訊的“”——STLFilt。這玩意幫你把編譯錯誤轉換成人能識別的自然語言,不錯是不錯。可惜STLFilt有了,BoostFilt呢?ACEFilt呢?我自己寫的模板庫呢?

其實,造成這個問題的直接原因是C++的型別系統的抽象層次太低。C++的靜態強(也有人說C++的型別系統其實是弱型別系統,anyway)型別系統所處的抽象層面是在基本型別(intdoublechar…)層面的。一方面,C++雖然擁有對自定義型別的上乘支援(比如,支援將自定義型別的介面裝扮得跟內建型別幾乎毫無二致——vector vs. build-in array),然而另一方面,C++的型別系統卻對於像vector這樣的抽象從語意上毫不知情。直接的後果就是,一個高層的型別錯誤往往以相差了十萬八千里的底層型別錯誤表現出來,結果就是你得充當一次福爾摩斯,從底層錯誤一直往上回溯最終找到問題的發生點。譬如一開始給出的那個例子:std::sort(li.begin(), li.end());的錯誤,如果C++型別系統的抽象層能高一些的話(所謂抽象層次高,就是知道高層抽象概念(Concept)的存在,如“隨機迭代器”這個概念),給出的錯誤無非就是:“list的迭代器不滿足隨機迭代器這個概念(concept)的要求(requirements)”。然而由於C++並不知道所謂concept的存在,所以問題到它眼裡就變成了“找不到匹配的operator+…”一堆nonsense

事兒#2

大二上學期的時候我們上一門計算方法的課程,期末考試要寫一些矩陣演算法。地球上的程式設計師大抵都知道矩陣演算法不用Matlab算基本等於沒事找抽,一大堆accidental complexities在那恭候著,一個index錯誤能讓你debug到抓狂。當時我C++用得半斤八兩,模板七竅也差不多通了六竅;為了到上機考試的時候節省點時間,就事先寫了一個簡單的矩陣庫,封裝了一些基本的操作和像高斯消元這種基本演算法。

那個時候你能指望我知道TDD?還是XP?或者?於是呢?寫了一個簡單的程式,簡單使用了一下寫好的庫,發現編譯通過後就興沖沖地告訴哥們說:大家不用怕,有我這Matrix庫罩著,寫演算法跟寫偽碼差不到哪去!

兩天後上機考試,程式不同了,等於測試用例不同了,結果原來沒有出現的編譯錯誤一下統統跑出來了。原來為什麼不出現?一個原因是原來有些成員函式就沒用到,C++說,在一個模板類裡面,沒用到的成員函式是不予編譯的。那不予編譯就代表不予糾錯嗎?不予型別檢查嗎?令人悲傷的是,的確如此。或者把置信度提高一點說,幾乎如此。為什麼?看看下面的程式碼:

template<typename T>

void f(T& t)

{

t.m();

}

你說編譯器看著這個函式,它怎麼做型別檢查?它怎麼知道t上面有沒有成員函式m?它連t的型別都不知道。“很久很久以前,模板就是這樣破壞模組式錯誤檢查的

實際上,C++98那會,為了能夠儘早儘量檢查模板程式碼中的隱患,以響應“防範勝於救災,隱患重於明火”的號召,C++甚至將模板上下文中的程式碼中的名字生生分成了兩類,一類叫dependent names,一類叫non-dependent names。舉個例子,上面那段程式碼中的m成員函式就是dependent的,因為它的隱含this引數t的型別是dependent的;對於dependent name,不作型別檢查——原因剛才講過,因為型別資訊根本就沒有。剩下的就是non-dependent names了,比如:

void g(double); // #1

template<typename T>

void f()

{

g(1);

}

void g(int); // #2

int main()

{

f<int>();

}

這裡f裡面呼叫的g繫結到哪呢?答案是#1。因為g是個non-dependent name(雖然它位於模板函式(上下文)裡面)。而對於non-dependent name,還是趕緊進行型別檢查和名字繫結吧,有錯誤的話也能早點暴露出來,於是g便在它的使用點“g(1)”處被查詢綁定了——儘管#2處的g(int)是一個更好的匹配,但在g(1)處只有g(double)是可見的,所以g(double)被編譯器看中了,可憐的g(int)只能感嘆“既生g(int),何生g(double)…”。

這,便是臭名昭著的腰斬…sorry…是二段式名字查詢,C++著名的複雜性來源之一。說它臭名昭著還有一個原因——在眾多編譯器支援良莠不齊的C++複雜特性中,它基本可以說是位居第一(第二估計要留給友元聲明瞭),VC掙扎到8.0還是沒有實現二段式名字查詢,而是把所有的工作留到模板例項化點上進行,結果就是上面的例子中會選中#2

D&E中對此亦有詳細介紹。

實際上,這個二段式名字查詢的種種問題正從一個側面證明了早期型別檢查是何等重要,動態語言的老大們在Ruby翻出來的舊瓶新酒Duck Typing吵翻了天其實說的也是這個問題(sorry,要加上“之一”)。

事兒#3

在一個無聊的午後,我在敲打一坨程式碼,這是一個演算法,演算法要用到一個容器,演算法是用模板來實現的:

template<typename ContainerT>

void XXXAlgo(ContainerT cont)

{

… cont.

在我敲打出“cont”加點號“.”之後,我習慣性地心理期待著“智慧”的IDE能夠告訴我cont上面有哪些成員函式,正如我們每次敲打出“std::cout.”之後一樣。習慣成自然,你能說我不對麼?難道你金山餈粑用久了不也一樣在讀影印版紙書遇到不認識單詞的時候想著把手指頭伸過去指著那個單詞等著跳出個詞條視窗來?難道只是我?咳咳

問題是,我知道XXXAlgo的那個模板引數ContainerT是應當符合STL的,我當然希望編譯器也能知道,從而根據Container概念所規定它必須具有的成員函式來給我一個成員函式列表提示(beginendsize…),難道這樣的要求很過分嗎?它沒有道理很過分啊,覺得它很過分我會說的啊,不可能它明明不過分我偏要說它很過分,他很過分我偏要說它不過分啊你覺得這要求過分你就說嘛亂敲鍵盤是不好滴,鍵帽掉下來砸到花花草草也不好啊你看,“.”鍵又給你磨平了

一方面,程式設計師一臉無辜地認為IDE應該能夠看到程式碼裡面的ContainerT暗示著這是一個符合STLContainer概念的型別。而另一方面IDE廠商卻也是理直氣壯:寫個ContainerT就了不起啊,萬一遇到個C過來的,寫成ContT我怎麼辦?寫成CntnrT哪?是不是要我實現一個?再說你覺得ContainerT是對應STLContainer概念的,別人還用這個單詞來對應執行緒池呢怎麼辦捏?什麼?他不知道“poor”怎麼寫管我啥事嘞?我身為一個IDE,根據既有的資訊,作出這樣的假設,既合情,也合理

事兒#4(此事純虛虛構,如有巧合,算你運氣背)

一天,PM跑過來告訴你說:“嘿,猜怎麼著,你寫的那坨模板程式碼,隔壁部門人用了說很不錯,希望你能把程式碼和文件完善一下,做成一個內部使用的庫,給大家用,如何?”你心頭一陣花枝亂顫:“靠!來部門這麼久了,C++手段終於可以展露一下了。”於是廢寢忘食地按照STL文件標準,遵照C++先賢們的教誨,寫了一個漂漂亮亮的文件出來。裡面Concept井井有條,Requirements一絲不苟

動態語言的老大們常掛在嘴邊的話是什麼?——需求總是在變的。又一天,你發現某個Concept需要revise了,比如原來的程式碼是這樣的:

template<typename XXX>

void f(XXX a)

{

a.m1();

}

本來XXX所屬的那個Concept只要求有m1成員函式。後來因需求變更,XXX上需要一個新的成員函式m2

相關推薦

C++0x漫談系列Concept, Concept!

《C++0x漫談》系列之:Concept, Concept! By 劉未鵬(pongba) 《C++0x漫談》系列導言 這個系列其實早就想寫了,斷斷續續關注C++0x也大約有兩年餘了,其間看著各個重要proposals一路review過來:rvalu

C++0x漫談系列瘦身前後——兼談語言進化

瘦身前後——兼談語言進化 By 劉未鵬(pongba) 《C++0x漫談》系列導言 這個系列其實早就想寫了,斷斷續續關注C++0x也大約有兩年餘了,其間看著各個重要proposals一路review過來:rvalue-references,concepts,memo

C++0x漫談系列右值引用(或“move語意與完美轉發”)(下)

《C++0x漫談》系列之:右值引用 或“move語意與完美轉發”(下) By 劉未鵬(pongba) 《C++0x漫談》系列導言 這個系列其實早就想寫了,斷斷續續關注C++0x也大約有兩年餘了,其間看著各個重要proposals一路review過來:rvalue-r

C#基礎拾遺系列使用ILSpy探索C#7.0新增功能點

第一部分: C#是一種通用的,型別安全的,面向物件的程式語言。有如下特點: (1)面向物件:c# 是面向物件的範例的一個豐富實現, 它包括封裝、繼承和多型性。C#面向物件的行為包括: 統一的型別系統 類與介面 屬性、方法、事件 (2)型別安全:C#還允許通過dynamic關鍵字動態

一分鐘開始持續整合系列C 語言 + Makefile

>作者:CODING - 朱增輝 ## 前言 make 工具非常強大,配合 makefile 檔案可以實現軟體的自動化構建,但是執行 make 命令依然需要經歷手動輸入執行、等待編譯完成、將目標檔案轉移到合適位置等過程,我們真正關心的是最終的輸出,卻在這些中間過程上浪費了很多時間。利用 CODIN

C++語言筆記系列十八——虛函數(1)

自己 語言 數據類型 說明 出現 adium 重定義 angle rac 1.C++中的多態 (1)多態性:同一個函數的調用能夠進行不同的操作,函數重載是實現多態的一種手段。 (2)聯編:在編譯階段進行聯接。即是在編譯階段將一個函數的調用點和函數

C++語言筆記系列二十——模版

輸出 類模板 pos 有一個 class 初始 個數 創建對象 example 1.隨意輸入兩個數x和y,輸出最大值max。 int max(int x, int y) {return x>y?x:y;} 2.函數模版 (1)用一種或者多

AR Drone系列使用ROS catkin創建package並使用cv_bridge實現對ar drone攝像頭數據的處理

ray 進行 mage exec source stp roc waitkey 效果 1 開發環境 Ubuntu 12.04 ROS Hydro 2 前提 可參考這篇blog:http://blog.csdn.net/yake827/arti

caffe日常坑系列undefined reference to symbol '_ZN2cv6String10deallocateEv'

iss ren and tor ssi symbols str mis locate 在使用caffe庫編譯C++時出現的 解決如下: /usr/bin/ld: /tmp/ccA5JGRP.o: undefined reference to symbol ‘_ZN2cv

Xtrabackup系列源碼安裝

xtrabackup 安裝 一、檢查依賴包 rpm -q cmake gcc gcc-c++ libaio libaio-devel automake autoconf bison libtool ncurses-devel libgcrypt-devel libev-devel libcurl-de

zookeeper系列獨立模式部署zookeeper服務

pat 觀察 系統環境 centos 復制 init pac 很多 編輯 一、簡述   獨立模式是部署zookeeper服務的三種模式中最簡單和最基礎的模式,只需一臺機器即可,獨立模式僅適用於學習,開發和生產都不建議使用獨立模式。本文介紹以獨立模式部署zookeeper服務

別扯那些沒用的系列forEach迴圈

序 寫Java程式碼的程式設計師,集合的遍歷是常有的事,用慣了for迴圈、while迴圈、do while迴圈,我們來點別的,JDK8 使用了新的forEach機制,結合streams,讓你的程式碼看上去更加簡潔、更加高階,便於後續的維護和閱讀。好,不說了,"talk is cheap, show me t

大資料調錯系列自己總結的myeclipse連線hadoop會出現的問題

在我們學習或者工作中開始hadoop程式的時候,往往會遇到一個問題,我們寫好的程式需要打成包放在叢集中執行,這無形中在浪費我們的時間,因為程式可以需要不斷的除錯,然後把最終程式放在叢集中即可。為了解決這個問題,現在我們配置遠端連線hadoop,遠端除錯的方法。 一段程式如下:獲取更多大資料視訊資料請加QQ群

大數據調錯系列自己總結的myeclipse連接hadoop會出現的問題

repair tput windows -o 32位 apache qq群 ins mark 在我們學習或者工作中開始hadoop程序的時候,往往會遇到一個問題,我們寫好的程序需要打成包放在集群中運行,這無形中在浪費我們的時間,因為程序可以需要不斷的調試,然後把最終程序放在

別扯那些沒用的系列Java異常

引子 先來一起看看下面的程式碼: package com.huangzx.Exception; /** * @author huangzx * @date 2018/11/27 */ public class ExceptionTypeTest { public class Exceptio

hbase系列初識hbase

一、概述   在hadoop生態圈裡,hbase可謂是鼎鼎大名。江湖傳言,hbase可以實現數十億行X數百萬列的實時查詢,可橫向擴充套件儲存空間。如果傳言為真,那得好好了解了解hbase。本文從概念上介紹hbase,稍微有點抽象,但這是學習hbase必須要了解的基礎理論;如果想直接瞭解hbase的實操內容,

zookeeper系列zookeeper簡介淺談

一、zookeeper的定義   開啟zookeeper官網,赫然一行大字,寫著:“Apache ZooKeeper致力於開發和維護實現高度可靠的分散式協調的開源伺服器”。什麼意思呢?就是Apache ZooKeeper的目標是開發和維護開源伺服器,這伺服器是幹什麼的呢?是做分散式協調的。這伺服器的特點是什麼

hbase系列獨立模式部署hbase

一、概述   在上一篇博文中,我簡要介紹了hbase的部分基礎概念,如果想初步瞭解hbase的理論,可以參看上一篇博文 hbase系列之:初識hbase 。本博文主要介紹獨立模式下部署hbase及hbase的幾個基本操作,需要具備一定的Linux基礎。 二、部署前準備   1、純淨的

C++刷題記錄集合union

前言 好久沒有寫C語言的題目了,畢竟現在在學習資料結構,還是要練習c++的,上課的時候老師提到一個萬能標頭檔案#include<bits/stdc++.h> 今天在一個平臺練習C語言的時候正好使用這個檔案頭,感覺挺輕鬆的,省去很多標頭檔案,接下來對題目進行簡單的分析。 題目描述:集合union

【 專欄 】- DevOps系列映象私庫

DevOps系列之:映象私庫 容器化是DevOps推動中一個重要的趨勢,這個專欄中將會介紹流行的映象私庫管理工具以及專案實踐經驗。