C++ 程序員如何用 D 編程
每個有經驗的 C++ 程序員都積累了一系列的習慣和技術,這幾乎成了第二天性。有時候,當學習一門新語言時,這些習慣會因爲太令人舒適而使人看不到新語言中等價的方法。所以下面收集了一些常用的 C++ 技術,以及如何在 D 中完成同樣的任務。定義構造函數
C++ 的方式
構造函數同類同名:Foo { Foo( x); };
D 的方式
構造函數用 this 關鍵字定義:Foo { ( x) { } }
基類初始化
C++ 的方式
基類構造函數通過參數初始化列表語法調用。A { A() {... } }; B : A { B( x) : A() // 調用基類構造函數 { ... } };
D 的方式
基類構造函數通過 super 關鍵字調用:A { () { ... } } B : A { ( x) { ... (); // 調用基類構造函數 ... } }D 的方式優於 C++ 的地方在於可以靈活的在派生類的構造函數中的任何地方調用基類構造函數。D 還可以讓一個構造函數調用另一個構造函數:
A { a; b; () { a = 7; b = foo(); } ( x) { (); a = x; } }也可以在調用構造函數之前初始化成員,所以上面的例子等價於:
A { a = 7; b; () { b = foo(); } ( x) { (); a = x; } }
比較結構
C++ 的方式
儘管 C++ 用簡單、便捷的方式定義了結構之間的賦值:A x, y; ... x = y;但這不適用於結構之間的比較。因此,如果要比較兩個結構實例之間的等價性的話:
A x, y; ==( A& x, A& y) { (memcmp(&x, &y, ( A)) == 0); } ... (x == y) ...注意對於每個需要比較的結構來說,都要進行運算符重載,並且對運算符的重載會拋棄所有的語言提供的型別檢查。C++ 的方式還有另一個問題,它不會檢查 (x == y) 真正會發生什麼,你不得不察看每一個被重載的 operator==() 以確定它們都做了些什麼。
如果在 operator==() 中使用 memcmp() 還回造成潛在而醜陋的 bug 。由於對齊的緣故,結構的內存分佈不一定是連續的,其中可能會有“洞”。C++ 並不保證這些用於對齊的洞中的值是確定的,所以兩個結構實例可能擁有完全相同的結構成員,但是卻因爲洞中含有不同的垃圾而不相等。
爲了解決這個問題,operator==() 可以實現爲按成員(memberwise)比較。不幸的是,這是不可靠的,因爲 (1) 如果一個成員被加入到結構定義中,程序員可能會忘記同時把它加到 operator==() 中,(2) 對於浮點數的 nan 值來說,就算它們按位比較相等,比較的結果也是不等。
在 C++ 中沒有健壯的解決方案。
D 的方式
D 的方式明顯而直接:A x, y; ... (x == y) ...
創造新的 typedef 型別
C++ 的方式
Typedef 在 C++ 中是弱的,就是說,它們不會真正引入一個新的型別。編譯器並不區分 typedef 和它底層的型別。*Handle; foo( *); bar(Handle); Handle h = HANDLE_INIT; foo(h); // 未被捕獲的編碼錯誤 bar(h); // okC++ 的解決方案是創建一個傀儡(dummy)結構,這個結構的唯一的目的就是獲得真正的新型別所具有的型別檢查和重載能力。
Handle { *ptr; Handle() { ptr = HANDLE_INIT; } // default initializer Handle( i) { ptr = ( *)i; } operator void*() { ptr; } // conversion to underlying type }; bar(Handle); Handle h; bar(h); h = func(); if (h != HANDLE_INIT) ...
D 的方式
不需要上面那種慣用的構造。只需要這樣寫:*Handle = ( *)-1; bar(Handle); Handle h; bar(h); h = func(); if (h != Handle.init) ...注意,可以給 typedef 提供一個默認的初始值作爲新型別的初始值。
友元
C++ 的方式
有時兩個類關係很緊密,它們之間不是繼承關係,但是它們需要互相訪問對方的私有成員。在 C++ 中這樣用到 friend 聲明:A { : a; : foo(B *j); friend B; friend abc(A *); }; B { : b; : bar(A *j); friend A; }; A::foo(B *j) { j->b; } B::bar(A *j) { j->a; } abc(A *p) { p->a; }
D 的方式
在 D 中,位於同一個模塊的類隱式地具有友元訪問權限。這樣做是有道理的,因爲關係緊密地類應該位於同一個模塊中,所以隱式地賦予位於同一個模塊中的其他類友元訪問權限是優雅的:X; A { : a; : foo(B j) { j.b; } } B { : b; : bar(A j) { j.a; } } abc(A p) { p.a; }private 特徵禁止從其他模塊中訪問成員。
運算符重載
C++ 的方式
假設有一個結構代表了一種新的算術類型,將其的運算符重載以使其可以和整數比較是很方便的:A { < ( i); <= ( i); > ( i); >= ( i); }; < ( i, A &a) { a > i; } <= ( i, A &a) { a >= i; } > ( i, A &a) { a < i; } >= ( i, A &a) { a <= i; }所有的 8 個函數缺一不可。
D 的方式
D 認識到比較運算符在根本上互相之間是有聯繫的。所以只用一個函數是必需的:A { opCmp( i); }編譯器依照 opCmp 函數自動解釋 <、<=、> 和 >= 運算符,並處理左操作數不是對象引用的情況。
類似這樣的明智的規則也適用於其他的運算符重載,這就使得 D 中的運算符重載不像在 C++ 中那樣繁瑣且易於出錯。只需要少得多的代碼,就可以達到相同的效果。
名字空間 using 聲明
C++ 的方式
C++ 中的 using 聲明 用來從一個名字空間作用域將名字引入當前的作用域:Foo { x; } Foo::x;
D 的方式
D 用模塊來代替名字空間和 #include 文件,用別名聲明來代替 using 聲明:---- Module Foo.d ------ Foo; x; ---- Another module ---- Foo; Foo.x x;別名比簡單的 using 聲明靈活得多。別名可以用來重命名符號,引用模板成員,引用嵌套類型別等。
RAII(資源獲得即初始化)
C++ 的方式
在 C++ 中,資源如內存等,都需要顯式的處理。因爲當退出當前作用域時會自動調用析構函數,RAII 可以通過將資源釋放代碼放進析構函數中實現:File { Handle *h; ~File() { h->release(); } };
D 的方式
大多數的資源釋放問題都是簡單的跟蹤並釋放內存。在 D 中這是由垃圾收集程序自動完成的。除了內存外,用得最普遍的資源要數信號量和鎖了,在 D 中可用 synchronized 聲明和語句自動管理。其餘少見的情況可用 auto 類處理。Auto 類退出其作用域時,會調用它們的析構函數。
File { Handle h; () { h.release(); } } test() { (...) { File f = File(); ... } }
屬性
C++ 的方式
人們常常會定義一個域,同時爲它提供面向對象的 get 和 set 函數:Abc { : setProperty( newproperty) { property = newproperty; } getProperty() { property; } : property; }; Abc a; a.setProperty(3); x = a.getProperty();所有這些都不過是增加了擊鍵的次數而已,並且還會使代碼變得不易閱讀,因爲其中充滿了 getProperty() 和 setProperty() 調用。
D 的方式
屬性可以使用正常的域語法 get 和 set,然後 get 和 set 會被編譯器用方法調用取代。Abc { property( newproperty) { myprop = newproperty; } property() { myprop; } : myprop; }使用時:
Abc a; a.property = 3; x = a.property;因此,在 D 中屬性可以被看作一個簡單的域名。開始時,屬性可以只是一個簡單的域名,但是如果後來需要將讀取和設置行爲改變爲函數調用,只需要改動類的定義就夠了。這樣就避免了定義 get 和 set 時敲入冗長的代碼,僅僅是爲了‘謹防’日後派生類有可能會不得不重載它們。這也是一種定義接口類的方法,這些類沒有數據域,只在語法上表現得好像它們作了實際工作。
遞歸模板
C++ 的方式
一種使用模板的高級方式是遞歸的擴展它們,依靠特化來終止遞歸。用來計算階乘的模板可能會是這樣:< n> factorial { : { result = n * factorial<n - 1>::result }; }; <> factorial<1> { : { result = 1 }; }; test() { printf("%d/n", factorial<4>::result); }
D 的方式
D 的版本與之相似,但是簡單一點,利用了將單一模板成員提升到外圍的名字空間的能力:factorial( n) { { factorial = n* .factorial!(n-1) } } factorial( n : 1) { { factorial = 1 } } test() { printf("%d/n", factorial!(4)); }