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