よくある質問
何度も訊かれる質問が増えてきので、 FAQ を用意しました。
D 2.0 FAQ
- D 2.0 と D 1.0 の違いは?
- 「シンプルな言語」はどこに行っちゃったの
- 他にどんなクールな機能が D 2.0 には予定されてるんだい?
- 僕が提案した最高の機能が無視されているのはなぜですか!
- なぜ const や immutable なんてものが必要?
- なぜ const や immutable なんていう 名前 なの?
- immutable と マルチコア の関係を正確に述べてください
- OK、スレッド間の安全なデータ共有におけるimmutableの利点はわかった。でもそういう意味での情報量のないconstがなんで必要なの?
- なぜ D 2.0 では immutable 文字列が好まれるのか?
- D 2.0 に貢献したいです。どうすればいいでしょうか?
- なぜ case range 式は case X..Y: という構文にしないの?
- shared が提供する保証は?
- shared と同期の関係は?
- shared とメモリバリアの関係は?
- unshared から shared へのキャストはどういう意味論になる?
- shared から unshared へのキャストはどういう意味論になる?
- なぜ大きな静的配列を使うとexeのファイルが増えるの?
D 全般の FAQ
- The D wiki FAQ page でもっと多くの質問への回答があります。
- C++ に無くて D にあるものは?
- なぜ D という名前なのですか?
- D という名前は変えられないの?検索エンジンで探しにくい。
- Dコンパイラはどこで手に入りますか?
- DはLinuxへ移植されていますか?
- GNU版のDはありますか?
- ××というCPU向けの自作のDコンパイラを書くにはどうしたらよいですか?
- DのGUIライブラリはどこで入手できますか?
- DのIDEはどこで入手できますか?
- printf という [-放送禁止-] なものを残したのは何故ですか?
- D はオープンソースですか?
- 標準ライブラリが Boost ライセンスなのは何故?パブリックドメインにしないのですか?
- switch文がfall throughなのは何故ですか?
- JavaではなくDを選ぶ理由としては何があるでしょう?
- C++でも、文字列はSTLでサポートされているのですが…
- C++でも、ガベージコレクションはライブラリを使えば可能なのではないでしょうか?
- C++でも、単体テストはライブラリを使えば可能なのではないでしょうか?
- 移植性を重視した言語なのにasm文があるのは何故ですか?
- 80bit実数型の利点は?
- D で無名構造体/共用体を使うには?
- 文字列に対して printf() を動作させるには?
- なぜ浮動小数点数は0ではなくNaNに初期化されるのですか?
- なぜ代入演算子のオーバーロードに対応していないのですか?
- ‘~’ は私のキーボードにないのですが…
- Can I link in C object files created with another compiler?
- 正規表現リテラルを /foo/g のような構文でサポートしないのはなぜですか?
- なぜDのフロントエンドはDではなくC++で書かれているのですか?
- Digital Mars のプログラムを全てDで書き直したらどうでしょう?
- どのような時にforよりもforeachループを使うとよいですか?
- Dに、CとのインターフェイスはあってもC++とのは無いのは何故?
- なぜDはガベージコレクションに参照カウント方式を使わないのですか?
- ガベージコレクションは遅くて予測不能なんでしょ?
- 十分に賢いコンパイラなら、関数がpureかどうか自動的に判別できるんじゃないの?
- cast(float) がその通りに動かないなら何故サポートしているの?
- ネストした関数が前方参照できないのはどうして?
なぜ case range 式は case X..Y: という構文にしないの?
case range statement を参照して下さい。
この構文にすると、.. の使い道は以下の3つになります:
- case X..Y:
- foreach(e; X..Y)
- array[X..Y]
shared が提供する保証は?
shared は、複数のスレッドがそのデータにアクセスしうることを意味します。 保証されるのは、sharedでなく、immutableでもないデータは、カレントスレッドからしか見えない、ということです。
shared と同期の関係は?
shared データのみが同期できます。 他の、スレッドローカルデータを同期する意味はありません。
shared とメモリバリアの関係は?
sharedデータへの読み書きの際には、逐次一貫性を保証するメモリバリアが生成されます (未実装です)。
unshared から shared へのキャストはどういう意味論になる?
そのデータへの他のunshared参照がないことが確実な場合のみ意味のある操作になります。
shared から unshared へのキャストはどういう意味論になる?
そのデータへの他のshared参照がないことが確実な場合のみ意味のある操作になります。
なぜ大きな静的配列を使うとexeのファイルが増えるの?
このような宣言があると:
char[1024 * 1024] arr;
実行ファイルのサイズは1MB増えます。 Cでは、arr がBSSセグメントに格納されるのでこれは起こりませんでした。 Dが arr BSSセグメントに格納しない理由は:
- char 型が、0 ではなく、0xFF に初期化されるため。 非ゼロのデータはBSSに配置することはできません。
- 静的に割り当てたデータはスレッドローカル記憶域に配置されるため。 BSSはスレッドローカルでは無く、BSSに相当するスレッドローカル領域はありません。
以下のように書くとBSSに配置されます:
__gshared byte[1024 * 1024] arr;
byte の初期値は 0 で、__gshared はデータをグローバルに割り当てるためです。
float, double, real の静的配列にも同様の問題があります。これらの型も、0 ではなく NaN (Not A Number) 値に初期化されるためです。
この問題に対処する一番簡単な方法は、 配列を静的にではなく実行時に動的に割り当てることです。
なぜ D という名前なのですか?
元々は プログラミング言語 Mars という名前でした。けれど友人が D と呼び続けたので、私も影響されて D と呼ぶようになったのです。 C の後継の D言語、という案は、 少なくとも1988年のこの スレッド までさかのぼります。
D という名前は変えられないの?検索エンジンで探しにくい。
変えられません。検索しにくくてストレスが溜まる、という点は承知していますが、 もう現時点となっては名前を変えるには遅すぎるところまで来てしまいました。検索語としては "dlang", "d programming", "d language", "d programming language" などを使うことをお勧めします。 こうすることでずっと良い検索結果が得られます。
公開されている D のコードの多くは "// Written in the D programming language" をコメントの最初に含んでいます。
DはLinuxへ移植されていますか?
はい。DigitalMarsのDコンパイラには Linux版 があります。
GNU版のDはありますか?
はい、 gdc - the D frontend with GCC です。
××というCPU向けの自作のDコンパイラを書くにはどうしたらよいですか?
Burton Radons が バックエンド を公開しています。参考となるでしょう。
DのGUIライブラリはどこで入手できますか?
D からは C の関数を呼び出せますので、Cのインターフェイスをもった GUI ライブラリなら、全てDからも使えます。D言語用の様々なGUIライブラリや移植版が、 the D wiki で紹介されています。
DのIDEはどこで入手できますか?
D 対応のIDEの一覧が the D wiki にあります。
D になぜ printf が残っているのですか?
printf は実際はDの一部ではなく、 しかしDからアクセスできるCの標準ライブラリです。 D の標準ライブラリは std.stdio.writefln, を含んでいて、これは printf 並に強力でしかも使いやすくなっています。
D はオープンソースですか?
D の dmd コンパイラのフロントエンドはオープンソースです。 dmd のバックエンドは Symantec からライセンスされていて、 GPL 等のオープンソースライセンスとは互換性がありません。 しかしながら、完全なソースコードが コンパイラ に附属し、開発は全て公開されて github で行われています。 DMD のフロントエンドに GCC や LLVM のオープンソースのバックエンドを合わせたコンパイラもあります。 ランタイムライブラリは完全にオープンソースで、 Boost License 1.0 でライセンスされています。 gdc と ldc は完全にオープンソースな D コンパイラです。
標準ライブラリが Boost ライセンスなのは何故?パブリックドメインにしないのですか?
ほとんどの地域の司法にはパブリックドメインという概念がありますが、 そうでない国 (例えば日本) もあります。 Boost License はこの問題を回避しています。 このライセンスが選ばれた理由は、 多くのオープンソースライセンスと違って、 バイナリ形式の配布にライセンス文を含めることを要求していないからです。
switch文がfall throughなのは何故ですか?
沢山の人に、switch 文のcaseとcaseの間には必ずbreakが入る、 という仕様にならないかと要求されました。C の、いわゆる "fall through" は沢山のバグの原因となってきたからです。
D2 では、暗黙の fall throught は禁止されました。fall throught したいときは goto case; を挿入して、 その意思を明示する必要があります。
これに加えてさらに break 文が暗黙の動作になるべきという提案もありました。 Dでこれを変えなかった理由は、整数の昇格や演算子の優先順位を同じにしたのと、 同様の理由 - Cとして読めるコードはCと同様に振る舞うべき - です。 潜在的に違う意味をもっていたりしたら、 余計わかりにくいバグを生むでしょうから。
JavaではなくDを選ぶ理由としては何があるでしょう?
D と Java では、目的も哲学も実現も異なっています。この 比較 をご覧下さい。
Java は 'write once, run everywhere' を目的に設計されました。対して D は、 効率的なネイティブアプリを書くために設計されています。DもJavaも ガベージコレクションは善で多重継承は悪で… というあたりで共通点はありますが、 目的の違いから、言語自体の感じも大きく違うものになっています。
C++では、文字列はSTLでサポートされているんですが…
C++の標準ライブラリには、 文字列や動的配列に連想配列、 境界チェック付き配列や複素数などが存在します。
もちろん、これらの機能は全てライブラリや、 何らかのコーディング方針を守ることなので実現できます。 しかしそれを言ったら、Cでオブジェクト指向だってできるのです (実際に目にしたことがあります)。どんな簡単な BASIC インタプリタでもサポートしている文字列というものために、 巨大で複雑な機構が必要というのはどうも不釣り合いです。 STLの文字列型は、実装に二千行以上のコードが費やされ、 高度なtemplateの機能を縦横に使っています。 これら全てが正しく動いているということをどれほど確信できるでしょう? もしまずい点があったらどうやってそれを直すのでしょう。 使っていてコンパイルエラーが出たときの猛烈に読みにくいエラーメッセージは どうすればよいのでしょう?自分の使い方が正しい(メモリリークが起きないとか…) ことをどうやって確かめればよいでしょうか?
Dの文字列実装は簡単で、直接的です。どう使えばいいか迷うようなことは ほとんどありませんし、メモリリークの心配もありません。 エラーメッセージは的を射ていて、期待通りに動作しているかどうかは 見ればすぐにわかります。
C++でも、ガベージコレクションはライブラリを使えば可能なのではないでしょうか?
はい、私も使っています。しかし、言語の一部でないという特性上、 これを使うにはいくつか制約が生じます。また一般のC++プログラマにとっては、 C++でgcを使うという選択肢はないようです。 Dのように言語内にGCを組み込むのは、 日々のプログラミングにとってとても実用的です。あまり先進的なことをしなければ、GCの実装は難しくありません。 簡単なものでも言語は100%動くという点で、より高度なGCを書くというのは、 より高度な最適化ルーチンを書く、ということに似ています。 規格をどの程度実装しているかというような点ではなく、 どの程度良いコードを生成するか、という点で複数の実装を比べられる方が、 プログラミングコミュニティにとってより良い状態です。
C++でも、単体テストはライブラリを使えば可能なのではないでしょうか?
もちろん。そのライブラリを使ってみて、Dと比べてみてください。 言語に組み込まれているというのがどんなに便利か、 すぐに明らかに感じるでしょう。移植性を重視した言語なのにasm文があるのは何故ですか?
asm文は、Dの関数内に直接アセンブリのコードを書き込むことを可能にします。 アセンブラのコードは自明に移植性のないものとなりますが、しかし、 システムアプリの開発には非常に便利なものです。システムアプリは 大なり小なりシステム依存のコードを含むことになるので、インラインアセンブラは 大した問題にはなりません。インラインアセンブラは、CPUの特殊命令や フラグビットへのアクセスによって、特別な処理の場合や、 限界までコードを最適化したいときに役立ちます。Cコンパイラがインラインアセンブラ機能を持つ前は、 私は外部のアセンブラを使っていました。アセンブラは 沢山の、本当に沢山のバージョンがあって、各バージョン毎に微妙に 構文が違っていたりバグがあったりコマンドラインの文法まで 変わっていたりして、非常に残念な思いをしました。 つまり、アセンブラの必要なコードは、ユーザーには安心してビルドはできなかったのです。 インラインアセンブラができてからは、そんなことはなくなりました。
80bit実数型の利点は?
有効桁数を増やせば増やすだけ、 正確に浮動小数点数の計算ができます。 これは特に大きな数と小さな数を足すときに顕著です。この話題については、 Intel の FPU をデザインした Kahan博士による、説得力のある 論文 があります。D で無名構造体/共用体を使うには?
import std.stdio;
struct Foo
{
union { int a; int b; }
struct { int c; int d; }
}
void main()
{
writefln(
"Foo.sizeof = %d, a.offset = %d, b.offset = %d, c.offset = %d, d.offset = %d",
Foo.sizeof,
Foo.a.offsetof,
Foo.b.offsetof,
Foo.c.offsetof,
Foo.d.offsetof);
}
文字列に対して printf() を動作させるには?
Cでは、文字列をprintfするには通常、書式 %s を使います:char s[8];
strcpy(s, "foo");
printf("string = '%s'\n", s);
D で次のようにこれを実行してみると:
char[] s;
s = "foo";
printf("string = '%s'\n", s);
大抵の場合、ゴミが表示されるかアクセス違反になります。
原因は、Cでは文字列は0終端であるためです。
%s fは 0 が現れるまで文字を出力しようとします。
Dでは、文字列は0終端ではなく、長さは別に保持された値で決定されます。
そこで、printfを文字列に使うには
書式 %.*s を使うことになります:
char[] s;
s = "foo";
printf("string = '%.*s'\n", s);
今度は期待したとおりに動作するでしょう。 しかし注意が必要なのは、printf の %.*s は、長さ分もしくは 0 が現れるまで 出力する、ということです。このため、中に0を含むDの文字列は、 最初の0までしか出力されません。
もちろん、もっと簡単な解決策は D の文字列に対して正しく動作する std.stdio.writefln を使うことです。
なぜ浮動小数点数は0ではなくNaNに初期化されるのですか
浮動小数点数は、明示的に初期化子が指定されていない限り、 NaN (Not A Number) へと初期化されます:double d; // d は double.nan に設定される
NaNは、演算のオペランドに使われると常に結果もNaNになるという、
素晴らしい特徴を備えています。従って、
NaNが計算課程にひとたび現れると、
これは伝搬し、出力としてNaNが得られることになります。
このことは、出力でのNaNの出現は、
どこかで未初期化変数が使われていることの証拠となることを示しています。
仮に 0.0 が浮動小数点数のデフォルト初期化子であったならば、 その結果は出力からは簡単には気づけなくなり、 そのデフォルト初期化値が意図したものではなかったときでも、 そのバグが見過ごされてしまうかもしれません。
デフォルト初期化値は、有用な値であることは意図されていません。 バグの発見がその用途です。そして、NaNはこの役割を適切に果たしています。
しかし、初期化されていない変数は当然コンパイラが見つけて、 エラーメッセージを出せるのではないでしょうか? ほとんどの場合確かにそれは可能ですが、常にではありません。この可能性は、 コンパイラ内部のデータフロー解析がどれだけ洗練されているかにかかっています。 それゆえ、そんな機能に頼るのは可搬性・信頼性に欠けます。
CPUがそう設計されたため、整数にはNaN値がありません。 そこで D では代わりに 0 を使います。 これは NaN のようなエラー検知には役立ちませんが、 少なくとも、意図しないデフォルト初期化に起因する結果は常に一定になり、 デバッグがより楽になります。
なぜ代入演算子のオーバーロードに対応していないのですか?
構造体に対する代入演算子のオーバーロードは D 2.0 でサポートされました。
‘~’ は私のキーボードにないのですが…
PCのキーボードでは、[Alt]を押しながらテンキーの 1, 2, 6 を順に押してAltを離すと、 ‘~’ が入力されます。
他のコンパイラで作ったCのオブジェクトファイルをリンクできますか?
DMDはOMF形式 (Microsoft Object Module Format) のオブジェクトファイルを生成しますが、他のコンパイラ、例えば VC++ はCOFF形式のオブジェクトファイルを生成します。 DMD の出力は、同じくOMF形式のオブジェクトファイルを生成する DMC (Digital Mars C コンパイラ) と共に動作するように設計されています。DMDの使うOMF形式は、かつてIntelが設計した形式に基づいて、Microsoft が定義したものに基づいています。Microsoftはある時から、 OMFの使用をやめて新しく定義したCOFFのMicrosoft版を使うようになりましたが。
同じオブジェクトファイル形式を採用しているからと言って、その形式のCライブラリが 正しくリンクできて実行できるとは限りません。実際にはもっと様々な互換性が必要です - 例えば、呼び出し規約や、名前マングリング、コンパイラの補助関数、 細かい動作に関する暗黙の仮定など -。例えDMDがMicrosoftのCOFFを出力できたとしても、 VCで使うために作られテストされたオブジェクトファイルを、 そのまま正しくリンクして実行できる可能性はほとんどありません。 Microsoftのコンパイラが実際にOMFを出力していた頃から、 この手の問題は多数存在していました。
相異なるオブジェクトファイル形式の存在は、DMD での動作がチェックされていないライブラリであることを示す役目を果たしています。 もしこうなっていなければ、リンクはどうにかできても、もっと見つけにくい問題の 素になってしまいます。あるコンパイラでビルドされたバイナリを、 別のベンダのコンパイラの出力と合わせて使えるようにするのは、 エキスパートの技が必要な作業なのです。
とは言えLinux版のDMDは、Linuxでの標準形式である ELF 形式のオブジェクトファイルを生成しますから、LinuxのCコンパイラとして標準的な gcc とは特にうまく協調できるようになっています。
既存のCライブラリを使うことができる場合も一つだけあります。それは、 そのライブラリがCのABIインターフェイスに適合したDLLとして提供されている場合です。 この場合ライブラリのリンク可能部分は "インポートライブラリ" と呼ばれ、 Microsoft の COFF形式のインポートライブラリは、 coff2omf を使えば DMD の OMF 形式へと正しく変換できます。
正規表現リテラルを /foo/g のような構文でサポートしないのはなぜですか?
理由は二つあります:
- / は除算トークンでもあるため、 /foo/g 構文を導入すると、 字句解析器と構文解析器を分離することが不可能になります。
- 現在、すでに3種類の文字列リテラルが存在し、正規表現リテラルを追加すると さらに3種類増えることになります。これは、コンパイラやデバッグ情報、 ライブラリなど多くの部分に波及しますが、それだけの価値がある変更ではありません。
なぜDのフロントエンドはDではなくC++で書かれているのですか?
フロントエンドがC++なのは、既存のgccやdmdのバックエンドとの整合性を とるためです。 他の既存のコンパイラバックエンドも多くはC++で書かれているので、 これは他のバックエンドへも簡単に移植できることにつながります。 DMDScript の D による実装 (C++版, よりもパフォーマンスが優れています) の存在は、 100% D でプロ品質のコンパイラを書くのに何の問題もないことを示しています。
Digital Mars のプログラムを全てDで書き直したらどうでしょう?
複雑でデバッグ済みで既に完成しているアプリケーションをわざわざ他言語に 移植することにあまり利点はありません。新しい Digital Mars のアプリは Dで実装されています。
どのような時にforよりもforeachループを使うとよいですか?
どのような時にforよりもforeachループを使うとよいですか?
foreachを使えば、プログラマが自分で考えなくても、 最適化のための選択を コンパイラに全て任せることができます。 例えば -- ポインタと添字のどちらを使うか? 終了条件をキャッシュしておくべきか否か? ループを回すべきか否か? これらの選択肢に対する最適解は自明ではありませんし、そもそもマシンによって 変わってきます。レジスタ割り当てなどと同じように、 こんな仕事はコンパイラに任せましょう。
for (int i = 0; i < foo.length; i++)
や:
for (int i = 0; i < foo.length; ++i)
や:
for (T* p = &foo[0]; p < &foo[length]; p++)
や:
T* pend = &foo[length];
for (T* p = &foo[0]; p < pend; ++p)
や:
T* pend = &foo[length];
T* p = &foo[0];
if (p < pend)
{
do
{
...
} while (++p < pend);
}
や、もちろん、size_t を使うべきか int を使ったほうがいいのか?
for (size_t i = 0; i < foo.length; i++)
こういった選択はコンパイラに選ばせましょう!
foreach (v; foo)
...
型Tが何であるべきかすら考えなくていいことにも注目してください。 これによって、Tが変化したときのバグの原因が減ります。fooが配列なのか、 連想配列なのか、あるいは構造体やコレクションクラスなのかすら気にする必要はありません。 これによって、ありがちな終了条件まわりのバグも避けられます:
for (int i = 0; i <= foo.length; i++)
さらに、fooが関数呼び出しだった場合に一時変数を手で作る手間も これによって回避できています。
forループを使う唯一の理由は、そのループが通常のループ形式に 合わない場合…つまり例えば その場で終了条件が変化する場合…であるときのみです。
Dに、CとのインターフェイスはあってもC++とのは無いのは何故?
D 2.0 には、制限された形ではありますが、 C++ とのインターフェイス があります。
以下に、このインターフェイスが完全でない理由を挙げます:D の C++ に対するインターフェイスを作るのは C++ のコンパイラを書くのとほとんど同じくらい複雑で、これは Dを十分実装しやすい言語とするという目標と矛盾してしまいます。 既存のC++コードを元に開発を続けなければならない人は、残念ながら C++から離れることはできないでしょう (D以外の言語であっても移ることは不可能です。)
変更できないC++のコードを任意にDから呼び出せるようにしようと思うと、 さまざまな困難が待ちかまえています。 以下のリストはもちろん完全ではなく、 いかに難しいかを示す例として挙げてあります。
- DのソースコードはUnicodeですが、C++はASCIIか、 あるいは指定されていません。これは文字列リテラルの内容に影響します。
- std::string はマルチバイトのUTFを適切に処理できません。
- C++のタグは個別の名前空間を持っていますが、Dにはありません。 従ってある種のリネームが必要になります。
- C++のコードはコンパイラの独自拡張に依存していることが多いです。
- C++ には名前空間があります。Dにはモジュールがあります。 この二つの対応関係は単純ではありません。
- C++ はソースコードを(プリプロセス後の)巨大なファイルと見なして処理します。 Dはモジュールとパッケージの階層として処理します。
- enumの名前スコープ規則が異なっています。
- C++のコードは、マクロをできるだけ使わないようにという長年の試みにもかかわらず、 マクロにマクロを重ねた層に依存しています。 Dには、トークン結合や 文字列化のようなマクロに対応するものがありません。
- マクロ名は #include を超えてグローバルなスコープを持ちますが、 巨大ソースファイルと見なすとローカルスコープです。
- C++ には多重継承や仮想継承があります。 Dにはありません。
- C++ はin,out,ref(inout)引数を区別しません。
- C++ の名前マングリング方式はコンパイラ毎に異なります。
- C++ は任意の型の例外(Objectの派生に限らない) を送出する可能性があります。
- C++ には const と volatile によるオーバーロードがありますが、 D には const と immutable によるオーバーロードがあります。
- C++ の演算子オーバーロード方式はだいぶDと違っています。例えば、 operator[]() の左辺値と右辺値としてのオーバーロードは、 const によるオーバーロードとプロキシクラスを使って実現されます。
- C++ の例えば < の演算子オーバーロードは 完全に > と独立しています。
- C++ はクラスと構造体を区別しません。
- 仮想関数テーブルの位置とレイアウトは C++ と D とで異なります。
- RTTI の実現方式もまったく異なります。 C++にはclassinfoはありません。
- D には two phase lookup や Koenig (ADL) lookup はありません。
- C++ はクラス同士を 'friend' で関連づけますが、D ではパッケージとモジュールに基づいて行われます。
- C++ のクラスデザインは、明示的なメモリ割り当ての問題を解決することを 念頭においておこなわれがちですが、Dではそうではありません。
- D のテンプレートシステムはだいぶ違っています。
- C++ には '例外仕様' があります。
- C++ にはグローバルな演算子オーバーロードがあります。
- C++ の名前マングリングは、const と volatile が型修飾子であることに依存しています。 一方で D の名前マングリングは const と immutable が型修飾子であることに依存しています。 また、D の const は C++ のそれと違い推移的なので、 D では mutable への const ポインタのようなものが表現できません。
問題の根底にあるのは、言語設計はその上で書かれるコードに影響する、 ということです。C++の言語設計はDにフィットしていません。 たとえ二者を自動的に繋ぐ方法を発見できたとしても、 その結果は左半分ホンダで右半分カマロのような大変なものになってしまうでしょう。
なぜDはガベージコレクションに参照カウント方式を使わないのですか?
参照カウント方式には利点もありますが、 いくつか重大な欠点があります:
- 循環データ構造が解放されません
- ポインタのコピーのたびに毎回参照カウントの上げ下げが必要です。 単に関数に参照を渡すだけのときであっても。
- マルチスレッド環境では、カウントの上げ下げに同期処理まで必要です。
- メモリリークを避けるために、カウントを減らす例外ハンドラ (finally) を挿入する必要があります。他の部分でどうがんばろうとも、 "オーバーヘッド無し例外" はありえないことになります。
- スライスや配列内部へのポインタに対応するには、あるいは同様に 非オブジェクトの任意のメモリ割り当てを参照カウントで管理するには、 独立した"ラッパー"オブジェクトを参照カウントしたいメモリ領域毎に 用意しなければなりません。これは本質的に、 必要なメモリ割り当て回数を倍増させます。
- ラッパオブジェクトが必要と言うことは、ポインタ経由のデータへの参照には、 常に2回ずつの参照外しが必要ということを意味します。
- コンパイラを調整してこれらすべてをプログラマから見えずに澄むようにしてしまうと、 Cとの綺麗なインターフェイスを整えるのが難しくなります。
- 典型的なケースでは総合的なメモリ消費はGCの方が大きくなりますが、 参照カウントはヒープを断片化させ、従って GC 以上にメモリを消費する可能性があります。
- 参照カウントはGCによる停止時間の問題をなくすわけではなく、 単に抑えるにすぎません。
C++標準として提案されている shared_ptr<> は参照カウントを実装し、 以上すべての欠点を抱えています。shared_ptr<> と Mark&Sweep を厳密に比較したベンチマークはまだ見たことがありませんが、 shared_ptr<> が速度とメモリ消費の両面で大負けしたとしても 私は驚きません。
とは言ったものの、D でも将来のオプションとしてある種の参照カウントを サポートする可能性があります。例えばファイルハンドルのような 個数の少ないリソースを管理するのには参照カウントは悪くない方式です。 さらに付け加えるならば、もし参照カウントでなければいけない理由があるならば、Phobos には std.typecons.RefCounted 型があり、 この機能を C++ の shared_ptr<> 同様ライブラリとして実装しています。
ガベージコレクションは遅くて予測不能なんでしょ?
その通りです。でも、malloc/free も含めて どんな 動的メモリ管理も、 遅くて、実行時間は予測不能なものです。 実際にリアルタイム系のソフトを作っている人に聞くと、 正確性が必要な場面では彼らはmalloc/freeは使わないと言います。 全てのデータをあらかじめアロケートしておくのです。 しかしながら、mallocの代わりにGCを使うことでより進んだ言語機能 (とりわけ、より強力な配列操作) が利用可能になり、 結果として必要となるメモリ割り当ての回数も減少します。 このため、明示的メモリ管理よりも GC の方が現実には高速になることを意味しています。
十分に賢いコンパイラなら、関数がpureかどうか自動的に判別できるんじゃないの?
コンパイラは pure 性(と safe 性、nothrow 性)を、function と delegate リテラルについては推論します。 通常の関数に対してこれを行わないのは幾つか理由があります:
- ほとんどの関数は、別の関数を呼び出し、そこからも更に他の関数が呼び出されます。 そのうちに、ソースがコンパイラにはわからないライブラリルーチンの呼び出しに行き着きます。 そのような関数は pure でないと仮定するしかありません。 pure 属性を導入することで、外部ライブラリのpureな関数は pureとマーク付けすることが可能になり、 解析が多くのケースでうまく動作するようになります。
- 仮想関数 (および、関数ポインタやdelegate) でユーザーによって拡張されることを意図されたコードでは、 コンパイラからはそれらがpureかpureでないかわからず、常にpureでないと仮定しなければなりません。
- プログラマがある関数をpureに書いたつもりで、 コンパイラがそうでないと判断した場合、これがプログラマに気づかれない可能性があります。 もっと悪いことに、プログラマが気づいたとしても、 なぜコンパイラがそう判断したのかを理解するのはいくらでも難しくなりえます - 果たしてプログラムのミスなのかコンパイラのバグなのか?
cast(float) がその通りに動かないなら何故サポートしているの?
浮動小数点数の規則では、 cast(real)cast(float) を cast(real) に変換するのは妥当な変換とされます。 なぜなら、 浮動小数点数の規則は以下の原理を念頭に置いているからです:
浮動小数点数の精度があがると動作がおかしくなるアルゴリズムは、間違っている。 浮動小数点の精度は常に最小であると考えるべきで、最大ではない。
最大精度に合法的に依存してもよいプログラムは:
- コンパイラ/ライブラリの検証テストプログラム
- 精度をプログラム的に判定しようとするコード片
ですが、(1) は通常のプログラマの気にするところではありませんし、 精度を確かめるには他の手段もあります。
(2) については D にはその目的のためのプロパティが用意されています。
最大精度に依存するプログラムは、再考するか再設計の必要があります。
ネストした関数が前方参照できないのはどうして?
関数内の宣言は、モジュールスコープでの宣言とは異なります。 関数内では、変数の初期化が書いた順で実行されると保証されており、 ネストした関数は、その上で宣言されたすべての変数を参照できてしまうため、 これを好きに前方参照できるようにしてしまうと初期化順の保証を壊す可能性があるのです。
int first() { return second(); }
int x = first(); // x は y に依存しているが、y は未初期化
int y = x + 1;
int second() { return y; }
しかし、(例えば相互再帰したネスト関数など)ネスト関数の前方参照が必要になることも時にはあります。 もっとも一般的な解決策は、ローカルのネスト構造体を作ることです。 構造体の関数(それと変数も!)は、順序に関する制約を持ちませんので、 相互に前方参照することができます。