はじめに
以前から「そのうち消すから使わないでね」と言われていたが、いつまでたっても消えないので、「このまま残ったままになるのでは・・・」と思われていたdelete
演算子ですが、dmd2.079.0(2018年3月1日リリース)で__ついに__廃止予定となりました。一定期間の後言語から削除される予定です。
(ちなみにdmd2.080.0で、カスタムアロケータ(クラスに独自のnew
/delete
を定義するやつ)もひっそりと廃止予定となりました。)
変更からだいぶ時間が経っているのですが、ネタがない気付いたのが最近だったのと一応年内の出来事なので、今さらですが書いてみることにしました。
代替の機能
以下はdelete
を使用した際に表示される警告メッセージです。
Deprecation: The `delete` keyword has been deprecated.
Use `object.destroy()`
(and `core.memory.GC.free()` if applicable) instead.
代替機能としてobject.destroy()
が案内されています。
故delete
の動作は大まかには次の通りです。
- オブジェクトの破棄(デストラクタ)
- メモリの解放
- 参照変数の初期化(nullの代入)
このうち1.を行うのがobject.destroy()
であり、2.を行うのがcore.memory.GC.free()
に相当します。
元の挙動が__どうしても__必要な場合、core.memory.__delete()
を使用することもできます。
D言語的にはメモリの解放はGCに任せてほしい、という感じでしょうか。
object.destroy()
の動作(クラスの場合)は
- 初回のみ以下の処理を実行する
- メンバ変数、および本体のデストラクタを再帰的に呼び出す
- メンバ変数を初期状態に戻す
という感じで、object.destroy()
後にオブジェクトに触っても(ある程度)安全なようになっています。
int dtorCount;
class Hoge {
string s = "XXXX";
this() { s = "Hoge"; }
~this() { ++dtorCount; }
}
void main() {
auto p = new Hoge();
assert(dtorCount == 0);
assert(p.s == "Hoge");
.destroy(p); // 1回目は実行される
assert(dtorCount == 1);
assert(p.s == "XXXX"); // 初期状態に戻っている
.destroy(p); // 2回目以降は何もしない
assert(dtorCount == 1);
assert(p.s == "XXXX");
}
注意点として、object.destroy()
はGCにはノータッチ(GCを実行したりメモリを解放したりしない)とドキュメントには書いてあるのですが、現時点では@nogc
ではないようです(object.destroy()
の問題ではなく、クラスのデストラクタの問題)。
class Hoge {
void print(string s) @nogc {
import core.stdc.stdio;
fprintf(stdout, "%s", s.ptr);
}
}
void main() @nogc {
import core.stdc.stdlib, std.conv;
// GCを使わずに、malloc, freeでメモリ確保
enum size = __traits(classInstanceSize, Hoge);
void* mem = malloc(size);
scope(exit) free(mem);
// emplaceでインスタンスを割り当て
auto hoge = emplace!Hoge(mem[0 .. size]);
// 普通に使用できる
hoge.print("hello");
.destroy(hoge); // Error: `@nogc` function `D main` cannot call non-@nogc function `object.destroy!(true, Hoge).destroy`
}
class Hoge { }
void main() @nogc {
import std.experimental.allocator;
import std.experimental.allocator.mallocator;
auto p = Mallocator.instance.make!(Hoge)();
// 同じ理由でこれもダメ
Mallocator.instance.dispose(p); // Error: `@nogc` function `D main` cannot call non-@nogc function `std.experimental.allocator.dispose!(shared(Mallocator), Hoge).dispose`
}
すぐには修正が難しい問題らしく、ちょっと残念。
廃止の理由
公式サイトのDeprecated Features(廃止予定の言語機能についてまとめたページ)のdelete
の項には
delete makes assumptions about the type of garbage collector available that limits which implementations can be used, and can be replaced by a library solution.
delete
が存在することで、D言語で使用可能なGCの種類が制限され、またライブラリ実装で置き換えが可能なため、とあります。
(※ちなみにInternetArchiveによると、2012年7月2日には既にこの記載がありました・・・。)
これだけだと、delete
は標準のGC実装に依存しているので、引き剥がすことが目的という風にも読めます。
ここがダメだよdelete
delete
の問題点として思いついたものを書いてみます。
- オブジェクトへの参照が複数ある場合、
delete
後も解放済みのメモリにアクセス可能になる
import std.stdio;
class Hoge {
string s_ = "XXXX";
this(string s) { s_ = s; }
~this() { "%s.dtor()".writefln(s_); }
}
void main() {
/// gc
{
scope a1 = new Hoge("a"); // オブジェクトを参照するa1とa2が消えてから
scope a2 = a1; // GCによってファイナライズされるので安全
}
"---".writeln();
/// delete
{
scope b1 = new Hoge("b");
scope b2 = b1;
delete b1; // オブジェクトは削除され、b1はnullになるが
b2.__dtor(); // b2経由で解放済みのメモリにアクセス可能であり危険
}
}
a.dtor()
---
b.dtor()
XXXX.dtor()
-
const
/immutable
な変数の場合、delete
では最後のnull
の代入ができずにエラーになる(それはそう)
class Hoge { }
void main() {
import core.memory;
const p = new Hoge();
delete p; // Error: cannot modify `const` expression `p`
.destroy(p); // Ok
GC.free(cast(void*)p); // Ok
}
おわりに
今年はあまりD言語の情報を追えていなかったのですが、地味に色々と進化していて面白かったです。