参照恐い

D において変数を同じ型の別の変数へ代入したとき、

・基本型、構造体は値渡し
・class 型は参照渡し

でコピーが行われる。
このことに曖昧さはない。
曖昧なのは配列のコピーである。
以下コード。

import house.tostring;

struct S {
	private char[] value;
	static S opCall(char[] value) {
		S s;
		s.value = value;
		return s;
	}
	char[] toString() {
		return "{" ~ value ~ "}";
	}
}

import std.stdio;
void main() {
	char[] str = "ABC";
	S s1 = S(str);
	S s2 = s1;
	str.writefln();
	ToString(s1).writefln();
	ToString(s2).writefln();

	str[1] = 'b';
	str.writefln();
	ToString(s1).writefln();
	ToString(s2).writefln();
}

...
ABC
{ABC}
{ABC}
AbC
{AbC}
{AbC}

あ〜あ、しっかり共有されちゃってる。
値のコピーが当たり前な C++ になじんだ者してはかなり異質に感じる。


静的配列はどうであろうか?

struct S {
	private char[3] value;
	static S opCall(char[] value) {
		S s;
		s.value[] = value;
		return s;
	}
// 以下同文

...
ABC
{ABC}
{ABC}
AbC
{ABC}
{ABC}

おぉ、これぞ求めていたものだ。
静的配列の場合、宣言時に領域が確保されるので、代入によって参照が共有されることはないのね。


動的配列で参照が共有されないようにするには、dup を使わにゃならんなぁ。
めんどくせぇなぁ。

struct S {
	private char[] value;
	static S opCall(char[] value) {
		S s;
		s.value = value.dup;
		return s;
	}
	S dup() { return S(value); }
	char[] toString() {
		return "{" ~ value ~ "}";
	}
}

import std.stdio;
void main() {
	char[] str = "ABC";
	S s1 = S(str);
	S s2 = s1.dup;
	str.writefln();
	ToString(s1).writefln();
	ToString(s2).writefln();

	str[1] = 'b';
	s1.value[2] = 'c';
	str.writefln();
	ToString(s1).writefln();
	ToString(s2).writefln();
}

...
ABC
{ABC}
{ABC}
AbC
{ABc}
{ABC}

OK,OK
でも、使ってもらう人にいちいち

	S s2 = s1.dup;

と書かせるのは心苦しいなぁ。
dup が必要なのは「struct S」の実装の都合なんだよなぁ。
まぁ、copy on write に慣れた方なら「そーいうもんなんだ」と納得してくれるかもだが、やはり心苦しい。


動的配列ってポインターみたいなもんと割り切るしかないですねぇ。
Java から D に来る人も C++ から来る人もとまどいそうだ。


ところで配列と言えば連想配列もあるが、連想配列は動的なもんだから値によるコピーはまずダメでしょうな。

import tostring;

struct S {
	char[][char[]] map;
	static S opCall(char[][char[]] map) {
		S s;
		s.map = map;
		return s;
	}
	char[] toString() {
		return "{" ~ ToString(map) ~ "}";
	}
}

import std.string;
void main() {
	char[][char[]] map;
	map["ABC"] = "abc";
	map["DEF"] = "def";
	map["GHI"] = "ghi";
	S s1 = S(map);
	S s2 = s1;

	ToString(map).writefln();
	ToString(s1).writefln();
	ToString(s2).writefln();

	map.keys[0][1] = 'Z';
	map.values[1][1] = 'z';
	ToString(map).writefln();
	ToString(s1).writefln();
	ToString(s2).writefln();
	
	writefln("ABC" in s1.map);
	writefln("AZC" in s1.map);
	writefln(s1.map["ABC"]);
	writefln(s1.map["AZC"]);
}

...
[(ABC:abc),(DEF:def),(GHI:ghi)]
{[(ABC:abc),(DEF:def),(GHI:ghi)]}
{[(ABC:abc),(DEF:def),(GHI:ghi)]}
[(AZC:abc),(DEF:dzf),(GHI:ghi)]
{[(AZC:abc),(DEF:dzf),(GHI:ghi)]}
{[(AZC:abc),(DEF:dzf),(GHI:ghi)]}
0
0
Error: ArrayBoundsError refmap(36)

やっぱりねぇ。
しかもしっかり key が壊されてて順次アクセスしかできなくなってるし(笑)。


Java プログラマーには「共有してナニが悪いの? ナニが問題なの?」
ってなもんなんだろうか?