Translated by ChenChunhui
原帖的連結是:http://www.telegraph-road.org/writings/gtkmm_vs_qt.html
最後附有html格式的文檔
(Andi Peredri 為此文提供了 俄文翻譯 )
再論 Gtkmm 與 Qt
(讓我們期望這是最後一次了).
在這篇訪談裡,Murray Cumming,當前的gtkmm(以前被稱為 Gtk--)的維護者,談到了他認為我是為什麼離開了gtkmm的,和他心目中gtkmm相對於Qt的優勢。因為我不同意他那番話中說的很多東西,我覺得 我應該回復一下了。
為什麼我離開
Murray : (...)我猜想Guillaume從未真正地掌握gtkmm的代碼基礎,並抓住機會從令他沮喪的事情中逃離出來。(...)
我解釋了為什麼我離開gtkmm,而且“抓住機會從令他沮喪的事情中逃離出來” 絕對不是原因之一。不過你確實幾乎說對了一件事。我確實從未真正地掌握gtkmm的內在特性,因為這是Karl的領域,我比起Karl來說能花在 gtkmm上的閒置時間實在是太少了,並且對這些時間中幹的其中大多數事情都是不感興趣的。我的目標是獲得一個可以用於Rosegarden的工具包。所 以我在gtkmm上的主要活動是為小部件打包。
gtkmm 與 Inti
Murray : (...)我認為Guillaume無論如何都確信,由於RedHat的支持, Inti會成功而gtkmm會逐漸消亡。
呃,是的,我相信Inti會成功,而顯然我是錯的。我相信的原因大概是我認為, 當一個開發者可以專職為一個專案工作的時候,那個專案就能進展得很快。但是這並沒有發生,因為Havoc最終還是把它大部分的時間花到了GTK+ 2上。
Murray : (...)絕大多數gtkmm的用戶和開發者都強烈反對Inti的設計決定
據我所知, “絕大多數”在這裡是指6人左右,或者根據我在檔案裡讀到的線索。這6人裡最重要的一人顯然就是Karl,他的觀點我至今都還反對。我不需要一個“駭客工 具箱”,我需要一個能運轉的且易用的的工具箱。
社區開發
Murray : (...)Inti消亡了,因為它從未涉及到任何一個開發者社區(...)
(...)基本上,QT並不是公開開發的,所以在沒有受益於任何 真正的批評的情況下犯了一些錯誤。(...)另外也許更重要的是,如果你在使用gtkmm的時候遇到了什麼問題你可以提交一個補丁或與其它開發者進行討 論。
在我看來,我認為更確切的說Inti消亡的原因是它的維護者的停止維護。對於Qt來說, 確實它不是公開開發的, 你猜怎麼樣: 它很棒.
你似乎在暗示公開開發意味著品質。這是一個舊的自由軟體的神話。一 群自認為知道自己在討論什麼東西的人們(我確實曾經是當中的一員)於閒置時間相互收發的電子郵件並不能保證什麼東西。但是由一群技能熟練的人組成的一個天 天為難題而工作的、接收用戶回饋的、能當面討論問題的小團隊很有可能達成某些好事。
讓我們面對事實吧:好的程式師並不是氾濫成災, 即使是在自由軟體世界。儘管區別就是很多人並沒有意識到他們的真實技術水準。所以讓很多人來參與困難問題的決定沒有什麼好處,只會讓事情變得更糟。
所有的gtkmm 的設計決定都是公開詳盡地討論的,就這樣仍有一些決定是錯誤的(像記憶體管理問題),這主要是因為參與討論的人裡面沒有一個真正在使用gtkmm來開發實現 的應用程式。我們全都太拘泥於創建一個“真正的C++ API”,而不是製作一個便於使用的東西。我在上面給出連結的Karl的帖子對於這個來說是一個極好的例子。一個由Matthew Thomas 製作、在許多網站上被充分討論的網頁日誌條目,切中肯綮。它講述的是使用者介面設計,但是裡面絕大多數也可以用於API的設計。無論是對用戶還是開發者,界 面的設計要面對相同的問題:平衡標準、美學和務實。
之所以說Qt好是因為奇趣科技(Trolltech)的開發者是很厲害的,並且他 們確實聽取了他們客戶的意見(我知道這些第一手情況因為我是他們中的一員)。不僅如此,他們的客戶在創造大型的、真實世界中的應用程式。他們的客戶並不關 心“信守C++精神”,他們關心的是解決實在的問題和落實應該做的事情。Qt的開發者並不試圖預言什麼是一個好的API以及什麼不是,他們有大約2000 多位元客戶讓他們的選擇選擇面臨實踐的檢驗。
我並沒有說Qt的API是完美的,它確實是有些瑕疵的。我是說它工作得非常好, 我說這話的理由是我在過去兩年在兩個不同的專案(Rosegarden和為雇主的內部開發)上天天都在使用它。你在開發一個更好的東西的時候會比較艱難, 而我並不相信gtkmm正朝著那個方向發展,感覺它的發展方向恰恰相反。
最後,你可以向Qt的開發者們提交一個補丁並與他們討論問 題,因為客戶支援是他們工作的一部分。我知道這些第一手情況因為我自己就幹過這樣的事情,KDE的開發者們也是這樣的。
Qt對 C++的“擴展”
Murray : (...)這裡面主要的問題是它對C++語言的修改以及對自己非標準的字串類的使用。如我們所證實,這是不必要的。
QString是毫無疑問非常必要的,因為std::string不能處理 Unicode。gtkmm自己也有Glib::ustring,我看到你 把它的介面製作得與 std::string相似,這樣我還是認為這是個壞主意,因為不管它有多標準,它既不方便也不強大。QString提供了非常有用的特性諸如arg()、sprintf()、section()、simplifyWhiteSpace()、 toInt()和它的“兄妹”, setNum(),當然還有在日常開發中價值無法衡量的規則運算式的支援。
現在我們來談談長期以來頗具爭議的對C++語言的修改。你也許會說這並沒有必要。這是事實, 儘管你還沒有非常充分地證明,因為gtkmm沒有提供Qt的“擴展”做到的所有特性。
信號與槽
我明確地想知道槽和信號是用什麼符號表示的。在gtkmm裡,它們是模版函數,是你傳遞給你的連接調用的東西。在Qt裡它們是非常舊的const char*s。震驚!恐怖!QObject::connect把const char* 作為槽與信號的參數類型!這也就是我的第一反應。後來我認識到了兩件事:
• 我不介意。它可能會在審美觀上傷害我,但是它並不妨礙開發。
• 它是動態的。你可以自己產生這些字串並在運行時再決定把連接和被連接的東西。
上面的第二點對於我來說是最重要的。它為我當時正在開 發的內部應用程式節省了數周的開發時間。這個應用程式在啟動的時候就根據一個XML描述檔生成了它自己的使用者介面,而在這個檔裡面我可以像下面這樣寫上我自己的代碼:
- 代碼: 選擇全部
<connect source="widgetname" dest="someclass"
signal="buttonPressed()" slot="receivedButtonPress()" />
需要運用的代碼在半天之內就編寫完畢了。如今,我認為這可以用於Qt設計師。試試用libSigC來做相同的事,看看這樣會多“簡單”。
另外一個這種動態性帶來的特性就是松耦合。這就是說,在你 調用connect函數的時候你不需要完整的類型以及源物件和目標物件的類型定義。 你只需要知道它們是QObjects類。所以,假如連接是在代碼中的別處實現,而不 是在源物件或者目標物件的方法中,這種情況(如我在上面所描述的)下,執行它的函數不需要用“#include” 語句來包含源物件和目標物件的定義。代碼可以是這樣:
- 代碼: 選擇全部
void do_connect(QObject *source, QObject *destination)
{
QObject::connect(source, SIGNAL(buttonClicked()),
destination, SLOT(gotButtonClicked()));
}
不錯,你失去了編譯時的類型檢 查(但是你仍然可以在運行時進行檢查——如果connect()函數執行失敗你會得 到一個警告資訊),假如源物件或目標物件在定義中沒有一個正確的信號或槽,在這種情況下你不會遇到一個編譯錯誤,但是在實踐中這仍舊讓你能幹得更快。
這種設計(兩個物件之間的連接是靠協力廠商來完成的)是很普遍的。一旦你意識到了信號不只是為了事件處理另外還可以在保持松耦合的情況下在物件間進行通 信,你就會很頻繁地去使用它。
即使是由源物件或目標物件來進行連接,你仍然會成功,你可以這樣:
- 代碼: 選擇全部
void MyWidget::do_connect(QObject *destination)
{
connect(this, SLOT(itemClicked(int)),
destination, SLOT(gotItemClicked(int)));
}
你不需要依靠目標物件的你可以自由修改的定義。大發展又再一次出現了。為什麼你認為所有新創造出來的語言,如Java或C#,擁 有這種靈活性?它只是讓你工作得更快。我不記得在我使用Qt的這兩年裡曾經被什麼槽/信號相關的程式缺陷給嚴重煩擾過。我曾想過這種類型檢測的缺失將回來 並找你的麻煩,但事實並不是這樣。警告資訊對於有效地追蹤問題已經足夠了。
更多有關這個主題的資訊可以在Qt文檔上找到。
元物件編譯器和被添加的關鍵字
這是對Qt質疑得最多的地方。這些你必須在你的自訂類的定義和代碼裡使用的“槽”、“信號”、 “發射”等關鍵字不是C++裡的,並且你完全可以不用它們。是的,這是真的。不過事實是你用它們更好。它們使你的代碼看起來清晰且便於閱讀。
奇趣科技(Trolltech)已經給出了關於為什麼Qt不為信號和槽使用範本的一個非常好的解釋,這個解釋我想我已經給你指出來過了。還有, 通過自己的經驗, 我已經驗證了他們的每個觀點, 我相信, 所有其他Qt用戶也一樣。
不過有諷刺意味的是他們中的一個觀點是語法 問題。既然你一直都在製作GTK+和Co.的“C語言中的物件導向的” API,你應該比大多數人更清楚這是對的。然而你和那些認為物件導向不過是“語法之糖”的人在做一樣的事。呃,就像類一樣,信號和槽是定義明確的概念,因 此在語言級(如果是那樣的話可以說)上實現它們是有意義的,比讓用戶單獨去對付它們好。
這樣看來你對槽/信號問題的解決方法就是全部 在現行C++的框架下實現它們,就像其它人在用純C語言來實現類一樣。我的解決方法就是硬著頭皮和讓“槽”與“信號”成為官方C++的關鍵字:-)。程式 是用C++庫還是用機器語言編寫的並不重要。真正重要的是你使用添加上的東西來向程式設計語言表達你的設計。
使用元物件編譯器這件事,在 我看來並不是什麼問題。如果你要寫一個自己的makefile,你只需要加上一行就可以了。另外,KDE的automake宏或Qt的qmake語句也可 以為你處理這個問題。在實踐中元物件編譯器並不會妨礙你。
Murray : (...)因而用gtkmm來與其它的C++ API進行結合更簡單。(...)
你看到了 用Qt與其它的C++ API進行結合時的問題了嗎?在Rosegarden裡我們很樂意使用STL(並且用得很多),除了STL本身的一些複雜的部分引起的外問題完全沒有其它 問題:-)。例如std::vector<QString>如預期般工作。
Murray : (...)我相信如果你喜歡C++,你就會喜歡gtkmm,如果你在學習C++,gtkmm就是一個更好的榜樣。(...)
我不能確定gtkmm對於C++初學者是一個“更好的榜樣”,但是你說“如果你喜歡C++你就會喜歡gtkmm”,以及你仍然沒有理解為什麼Qt更好, 是問題的核心。我並不喜歡C++。我並不關心C++。我關心的是編寫出一個能做有用事情的應用程式。所以我並不介意使用一個會向C++裡添加非標準關鍵字的類庫,只要它對程式設計有幫助。但是你關心C++,所以你就不能做到這點。你在乎這點卻無視它的缺陷,並且你沒有意識到由於對它的“堅定信仰”,你使生活變 得更困難而不更簡單。另外,這就恰好是使用Gnome的C語言的程式師如何高高興興地去掌握GTK+的物件系統,而他們沒有意識到可以用C++和Qt快得多地辦到相同的事,並且徹底地確信他們所做的事是“簡單”的,因為他們喜歡C語言且不需要去學習另一門語言。
標準並不是神聖的東西。 理論上你最好遵循它們,但是在實踐中並不一直是這樣的情況,很簡單,因為它們是由可能會犯錯誤的人類所設計出來的。工具和語言不應該被喜歡,它們應該被恰 當地使用。否則,當一個更好的選擇出現在你面前,你會忽略它,因為你不想放棄你自己喜歡的那一個。
Qt vs. gtkmm
在談到這點的時候,我願意對你在你的“GUADEC talk”裡提出的觀點發表不同意見。
數據容器
你說:QT有很多與現在的標準庫重複的地方,比如容器和類型資訊。
事實確實如此。但是:
• 自從Qt3以來,Qt的容器是與STL相容的(例如你可以把STL的運算法則應用在它們上)。
• 一些Qt的容器確實比它們的STL上的等價物簡單。例如,QPtrCollection::setAutoDelete()是 很有用的。
• 你也可以忽略Qt的RTTI,並使用C++的標準RTTI。我們是這樣做的,KDE也是這樣做的。
內存管理
啊,又是老生常談了。第一,你說Qt需要指標,這是錯的。你可以為控制項使用普通的變數(看這個例子),並且當你在使用 指標的時候你可以自行刪除它們。你可以這樣做,而一般你不會想要這樣做的。
假設你創建了一個包含許多控制和標記的相當複雜的對話類。 你顯然需要追蹤其控制,因為你需要瞭解它們的狀態,於是你讓它們保持為資料成員。但是標記和佈局控制項怎麼辦?為它們保持一個引用有什麼用呢?難道這樣:
- 代碼: 選擇全部
new QLabel("Some label here", vbox);
然後忘記它不是更簡單嗎(因為你知道Qt會為你刪除指針)?就我而言,確實,這樣更簡單。當你可以讓電腦為你去做記憶體管理時親自做記憶體管理並沒有什麼值得自豪的。它總會比你做得更好。
這樣你就是在聲稱把做枯燥乏味以及容易出錯的事情的可能性留給用戶是一件好事,因為這是符合“正統C++的選 擇”。我認為這些“正統C++的選擇”應該被任何一個好的C++類庫隱藏起來。難道std::string和 所有的STL容器會因為它們分配記憶體而找你的麻煩?它們不會。在最好的情況下你可以使用你自己的記憶體分配算符,因為有時候你可能會需要實現一些非常特殊的要求,但是這不適用於控制項。所以為什麼 Qt就不能有一點不一樣的地方呢?
容器控制項
在你的談話中你說 Qt的容器類和佈局類是分開的,這正和gtkmm在容器類上的選擇是相反的。我對你的觀點理解得不是很清楚。Qt擁有如QVBox一樣的控制項,這正好像gtkmm擁有VBox,這兩個控制項的的行為幾乎一樣(你只需把控制項添加到上面就行了,在Qt中這意味著以QVBOX為父控制項添加一個新的控制項,並不需要外加一個“add” 語句)。另外,Qt擁有可以讓你能進行更好調整的佈局管理的(如QGridLayout,它同比它的“簡化”但和它類似的QGrid相比,提供更多的選項)類。你當然可以在必要的情況下創建一個自己的佈局類。
所以這樣Qt明顯地提供了與gtkmm一樣的特性,甚至還有gtkmm沒有的特性。因此我不理解你的觀點。
學習和使用
我想 gtkmm使你的代碼更清晰也理協調。我認為它節省你學習時間並減少讓你煩惱的東西。
我自己 的經驗直接同這相矛盾。在使用Qt兩周以後,我精通它的程度比我曾經用gtkmm的時候對gtkmm的精通程度更勝一籌,約三年來我一直使用這個“該死 的”東西:-)。
在這兩年來我也在與那六七個幾乎全轉向Qt或Swing甚至正宗GTK+的“前gtkmm用戶”討論,他們告訴我的 是一樣的:gtkmm太複雜了,用起來太笨重了。我相信你已經在gtkmm2裡改進了API,但是你無能為力的是要對付你加入的許多另外的庫這樣的瑣事。 我也很奇怪為什麼Terraform這個幾乎是當時唯一使用gtkmm的大型應用程式,不再使用它了。
那麼讓我再來談談我自己關於 Qt的一些非常具普遍性的經驗吧:所有這些Qt的非圖形介面相關的類,如XML分析程式,執行緒類,日期和時間處理,規則運算式,等等,是非常有用的。 Qt的開發者認為它們不實用,所以不想加入它們,但是Qt的主顧對開發者提出加入它們的要求,這是因為主顧們需要它們。沒有這些特性的幫助你是編寫不出一 個時髦的桌面應用程式的。Rosegarden使用gzip壓縮的XML作為檔案格式。XML是一個顯而易見的選擇,大部分是因為Qt擁有一個它的分析 器。要用gtkmm做相同的事,我們需要與libxml連接。這意味著另外一些擁有不同API和或多或少文檔……等等的庫需要處理,我們甚至有可能需要封 裝它,這又會導致更多的代碼。
這實際上正代表了我使用gtkmm開發Rosegarden的無益嘗試(見Rosegarden開發曆 史以獲得更多細節)。我一開始試著創建一個可以把音符顯示在上面的簡單的控制項。但是等一下,我必須創建一個沒有多少實用價值的音符的點陣圖,並且要做到這點 我需要使用GDK和imlib,它們中沒有一個在當時是已經被封裝的(而且封裝者使用了一個與gtkmm的其它部分完全不同的記憶體管理策略)。於是我首先 要向這些封裝者裡加入材料。接下來我需要Gnome Canvas來把譜線和音符顯示到上面去。這又是沒有封裝的,所以又得封裝。這此這般,令人作嘔。現在libxml、vfs和其它我們用的工具也是一樣 的,KDE提供了它們,而它們的Gnome中的等價物還沒有被封裝(另外我這裡使用的“等價物”一詞所指得東西比較寬泛)。
在轉向 Qt/KDE後大概一個月裡,我取得的進展比過去3年還大。一段時間後,Chris和Rich重新加入進來,現在我們仍然進展穩定,因為程式架構並不妨 礙,甚至對於相當難的像IPC一樣的東西也是這樣。DCOP運轉得太好了,從甚至不知道該做什麼事情的時候起,讓Rosegarden對DCOP的消息有 反應,僅僅用了我20分鐘(我沒有撒謊)。大多數的使用者介面問題在幾小時之內就解決了,這樣我們就能集中精力對付實際的問題。剛才我隨便寫了一些關於 Rosegarden的開發的不是很有條理的想法,如果你需要更多的細節你可以去查閱一下。
關於“節省學習時間”,看看gtkmm的 這篇關於記憶體管理的頁面。和QT的記憶體管理比較一下。
總結
Murray : (...)Guillaume現在使用QT。他聲稱對他來說擁有一個完美運轉的工具比一個完美的API更重要。gtkmm2將很快變得穩定——這樣我們就 可以在gtkmm2裡同時擁有易用性和完美的API了。
是的,我使用Qt/KDE,並為此感 到高興,因為經過了這麼些年Rosegarden開始看起來像一個真正的應用程式,which has always been the point. 這在使用gtkmm的時候是絕對不會發生的。
和你所說的相反,gtkmm2離可以提供相同級別的特性還有幾年的差距。如果它能達到,它也會付出巨 大的資源代價,因為你必須把每個庫都添加到封裝者裡。即使擁有gtkmm、gnomemm、bonobomm(它暗示了與所有它們所封裝的Gnome的庫 所連接),你仍然沒有得到Qt及kde庫的所有特性。看看一個簡單控制項的層次結構,我數出了4個如下的“物件”:
• SigC::Object
• Glib::ObjectBase
• Glib::Object
• Gtk::Object
人們會對它們的記憶體衝突感到驚訝。
另外一個問題,即使gtkmm這幾年來一直穩定,為什麼使用它的應用程式卻很少呢?
我們大約一年前進行過相似的討論。看起來你的觀 點還是沒有改變:你因為個人喜好的緣故不喜歡Qt,但是你仍舊對為什麼使用它就會讓你的代碼難於編寫和維護這點沒有一個清晰的論據。我的經驗是Qt使編寫 及維護代碼成為一項簡單的任務。只要看看由它產生的軟體的價值就知道了。如果gtkmm被如此廣泛使用,你就會在這方面得分。這是自由軟體,所以更好的技 術應該流行,是吧?
你不喜歡Qt,因為它不符合你心目中一個C++類庫應該遵循的標準,如此而已。我認為你的想法是錯誤 的,gtkmm正好重複了這類讓C++獲得“艱深易錯的語言”名聲的錯誤:自身不夠詳盡和把太多的東西留給用戶。
如此我祝你的 gtkmm2好運,但我還要繼續使用Qt/KDE並在Rosegarden上幫助Rich和Chris。
________________________________________
P.S. : 一個gtkmm常見問題 #2.4的“補丁”
下面是對關於一些項目的回答的修正。
• gtkmm使用std::string、std::list、std::vector、 iterators,等等。Qt使用自訂的容器。
這是對的,但是:
o QString使用起來在功能和便利性上大大優於std::string,另外它 是基於Unicode編碼的。實際上你的確不能對使用者可見的字串使用std::string, 除非你計畫從不使你的軟體多國語言化並使它僅僅保持為英文版。
o 容器在大多數情況下是作為一個沒有好的STL運行環境的平臺的代替品。它們確實擁有幾個便利的特性(如對基於指標的容器的“自動刪除”),但是通常你的 確不會使用它們,除非Qt的API需要這樣。它們也是相容STL的, 這意味著你可以對它們使用STL的運算法則。只要把它們視作另一種容器……
• 在gtkmm裡可以使用普通的C++記憶體管理。Qt需要把所有的控制項都視為指標來進行處理,並且控制項的刪除交由父控制項來處理。
這句話徹底錯了。你可以把控制項視作變數來處理,不過顯然這樣做在及個別情況下才有意義(如創建一個對話方塊),另外你也可以在需要的時候自行刪除控制項。
• gtkmm的控制項的安排更加簡單。在Qt中,容器和佈局是分開的類,子控制項必須被同時添加到它們兩者中。
這也是錯的:看看QHBox、QVBox、QGroupBox和QGrid。只有在你需要對控制項如何放置有更高要求的時候,佈局類才是必要的。Qt的佈局類同樣 擁有一個autoAdd標誌位元,這樣可以減輕你向它們或父控制項添加控制項時的工作量。
Guillaume Laurent