配列の共有

D言語のリファレンスページを読んでいると、なにげに

char[] a = new char[20];

という記述が目に入った。

そっか配列を new する手もあるのだな。
new はポインタっぽいし、動的配列は宣言後追加か、Wikiに載ってる配列リテラルを使ってばっかしいたので、あんまし使わないでいたけど、構築時に領域とれるな。

この方法使うと、3/20に書いた構築時の入力引数と構造体内部の共有はさけられる。
( ~= で配列に追加してもできる)

import house.tostring;

struct S {
	private char[] value;
	static S opCall(char[] value) {
		S s;
		s.value = new char[value.length];
		s.value[] = value;
		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;
	str.writefln();
	ToString(s1).writefln();
	ToString(s2).writefln();
	writefln("s1.ptr = %0x", s1.value.ptr);
	writefln("s2.ptr = %0x", s2.value.ptr);

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

...
ABC
{ABC}
{ABC}
s1.ptr = 900fe0
s2.ptr = 900fe0
AbC
{ABc}
{ABc}

しかし、構造体同士の代入によって内部メンバの配列が共有するのは避けられないですねぇ。dup 使わないと。


お遊びで s1 が持っている配列に追加してみた。

	s1.value ~= "D";
	ToString(s1).writefln();
	ToString(s2).writefln();
	writefln("s1.ptr = %0x", s1.value.ptr);
	writefln("s1.length = %0x", s1.value.length);
	writefln("s2.ptr = %0x", s2.value.ptr);
	writefln("s2.length = %0x", s2.value.length);

...
{ABcD}
{ABc}
s1.ptr = 900fe0
s1.length = 4
s2.ptr = 900fe0
s2.length = 3

おや〜、配列の長さが違っちゃってますな。
s2 の配列は s1 のをスライシングの形で部分的に参照している様。


s2 の方にも追加してみよう。

	s2.value ~= "EF";
	ToString(s1).writefln();
	ToString(s2).writefln();
	writefln("s1.ptr = %0x", s1.value.ptr);
	writefln("s1.length = %0x", s1.value.length);
	writefln("s2.ptr = %0x", s2.value.ptr);
	writefln("s2.length = %0x", s2.value.length);

...
{ABcE}
{ABcEF}
s1.ptr = 900fe0
s1.length = 4
s2.ptr = 900fe0
s2.length = 5

わはは s1 の新しく追加した値が上書きされてる。
length は変化なし。


s1 の方の length を s2 に合わせてやるか。

	s1.value.length = 5;
	ToString(s1).writefln();
	ToString(s2).writefln();
	writefln("s1.length = %0x", s1.value.length);
	writefln("s2.length = %0x", s2.value.length);

...
{ABcE }
{ABcE }
s1.length = 5
s2.length = 5

う〜ん、s2 のはみ出た部分までがデフォルト初期化されてますなぁ。


で、結論。

・動的配列の共有は危険。ptr は共有されるが、length は共有されない。
・共有したいときは、参照用に。共有後、length を変えるようなことをすると、予測のつきにくい動作になる。
・動的に共有したいときは、配列をラップした class を作って(作んのかよ)それを共有すること。

でした。


連想配列は安全に共有できるかな?(今後の課題)