8

D2 の動的配列を少し調べたところ、理解するのが非常に難しいことがわかりました。また、仕様を間違って解釈しているようです..動的配列の参照またはスライスでの作業は、配列を変更するときに非常にエラーが発生しやすいようです...または、基本を理解していないだけですか?

同じ配列を参照すると、実際の項目のみが共有されます。

auto a = [1];
auto b = a;
assert(&a != &b); // different instance; Doesn't share length
assert(a.ptr == b.ptr); // same items
assert(a == [1]);
assert(a == b);

それらは同じ配列を参照するため、一方を変更すると他方が変更されます。

auto a = [1,2];
auto b = a;
a[1] = 20;
assert(a == [1,20]);
assert(a == b);

配列の仕様から

効率を最大化するために、ランタイムは常に配列のサイズを変更して余分なコピーを回避しようとします。新しいサイズが大きく、配列が new 演算子または以前のサイズ変更操作によって割り当てられていない場合は、常にコピーが実行されます。

したがって、長さを変更しても、必ずしも参照が壊れることはありません。

auto a = [1];
auto b = a;
b.length = 2;
assert(b == [1,0]);
assert(a == [1]); // a unchanged even if it refers to the same instance
assert(a.ptr == b.ptr);  // but still the same instance

// So updates to one works on the other
a[0]  = 10;
assert(a == [10]);
assert(b == [10,0]);

配列の仕様から

オペランドの 1 つが長さ 0 の配列であっても、連結は常にそのオペランドのコピーを作成します。

auto a = [1];
auto b = a;
b ~= 2; // Should make a copy, right..?
assert(a == [1]);
assert(b == [1,2]);
assert(a != b);
assert(a4.ptr == b.ptr); // But it's still the same instance
a[0] = 10;
assert(b == [10,2]); // So changes to a changes b

しかし、配列が互いにステップする場合、値は新しい場所にコピーされ、参照が壊れます。

auto a = [1];
auto b = a;
b ~= 2;
assert(a == [1]);
assert(b == [1,2]);

a.length = 2; // Copies values to new memory location to not overwrite b's changes
assert(a.ptr != b.ptr);

変更を行う前に両方の配列の長さを変更すると、上記と同じ結果が得られます (上記を考えると、これが予想されます)。

auto a = [1];
auto b = a;
a.length = 2;
b.length = 2;
a[1] = 2;
assert(a == [1,2]);
assert(b == [1,0]);
assert(a.ptr != b.ptr);

長さを変更したり、カンカテーションしたりする場合も同じです(上記を考えると、これが予想されます):

auto a = [1];
auto b = a;
b.length = 2;
a ~= 2;
assert(a == [1,2]);
assert(b == [1,0]);
assert(a.ptr != b.ptr);

しかし、スライスも登場し、突然、さらに複雑になります! スライスは孤立している可能性があります...

auto a = [1,2,3];
auto b = a;
auto slice = a[1..$]; // [2,3];
slice[0] = 20;
assert(a == [1,20,3]);
assert(a == b);

a.length = 4;
assert(a == [1,20,3,0]);
slice[0] = 200;
assert(b == [1,200,3]); // the reference to b is still valid.
assert(a == [1, 20, 3, 0]); // but the reference to a is now invalid..

b ~= 4;
// Now both references is invalid and the slice is orphan...
// What does the slice modify?
assert(a.ptr != b.ptr);
slice[0] = 2000;
assert(slice == [2000,3]);
assert(a == [1,20,3,0]); 
assert(b == [1,200,3,4]);

だから...同じ動的配列への複数の参照を持つことは悪い習慣ですか? そして、スライスを渡すなど?それとも、D の動的配列のポイント全体を見逃して、ここから抜け出してしまったのでしょうか。

4

2 に答える 2

10

全体として、あなたは物事をかなりよく理解しているようですが、ptrプロパティの目的を誤解しているようです。2 つの配列が同じインスタンスを参照しているかどうかは示しません。それが行うことは、事実上その下にある C 配列へのポインターを取得することです。D の配列はそのlength一部であるため、C 配列よりも長さと C 配列へのポインターを持つ構造体に似ています。ptrC 配列を取得して、C または C++ コードに渡すことができます。おそらく、純粋な D コードでは何にも使用しないでください。2 つの配列変数が同じインスタンスを参照しているかどうかをテストする場合は、is演算子を使用します (または!is、それらが異なるインスタンスであることを確認します)。

assert(a is b);   //checks that they're the same instance
assert(a !is b);  //checks that they're *not* the same instance

ptr2 つの配列が等しいということは、最初の要素がメモリ内の同じ場所にあることを示しています。特に、それらlengthの は異なる可能性があります。ただし、いずれかの配列で重複する要素を変更すると、両方の配列で重複する要素が変更されることを意味します。

配列の を変更する場合length、D は再割り当てを回避しようとしますが、再割り当てを決定する可能性があるため、再割り当てするかどうかに必ずしも依存することはできません。たとえば、そうしないと別の配列のメモリ ( に同じ値を持つものを含むptr) を圧迫する場合、再割り当てします。その場でサイズを変更するのに十分なメモリがない場合は、再割り当てすることもできます。基本的に、そうしないと再割り当てされ、別の配列のメモリが圧迫されます。そうでない場合は、再割り当てされる場合とされない場合があります。したがって、一般に、配列を設定するときに配列が再割り当てされるかどうかに依存することはお勧めできませんlength

ドキュメントごとに常にコピーするように追加することを期待していましたが、テストごとに、それは同じように動作するように見えますlength(それがドキュメントを更新する必要があることを意味するのか、それともバグなのかはわかりません-私の推測ではドキュメントを更新する必要があります)。どちらの場合でも、追加後に同じ配列を参照するために、その配列への他の参照に依存することはできません。

スライスに関しては、期待どおりに機能し、D でよく使用されます。特に、標準ライブラリの Phobos で使用されます。スライスは配列の範囲であり、範囲は Phobos の中心的な概念です。ただし、他の多くの範囲と同様に、範囲/スライスのコンテナーを変更すると、その範囲/スライスが無効になる可能性があります。そのため、Phobos でコンテナのサイズを変更できる関数を使用している場合、そのコンテナの範囲を無効にする危険を冒したくない場合は、前に安定 (stableRemove()または) を付けた関数を使用する必要があります。stableInsert()

また、スライスは、それが指す配列と同様の配列です。したがって、当然のことながら、その変更lengthまたは追加は、他の配列の変更または追加と同じルールのすべてに従うlengthため、再割り当てされ、別の配列へのスライスではなくなります。

ほとんどの場合、配列の を何らかの方法で変更するlengthと再割り当てが発生する可能性があることに注意する必要があるため、参照が同じ配列インスタンスを参照し続けたい場合は、再割り当てを避ける必要があります。また、それらが同じ参照を指していないことを絶対に確認する必要がある場合は、 を使用dupして配列の新しいコピーを取得する必要があります。配列の をまったくいじらない場合length、配列参照 (スライスであろうと配列全体への参照であろうと) は引き続き同じ配列を問題なく参照します。

編集:ドキュメントを更新する必要があることがわかりました。配列のサイズを変更できるものは、可能な場合はその場でそれを実行しようとしますが (したがって、再割り当てされない可能性があります)、別の配列のメモリを踏みにじるのを避けるために必要な場合、または十分なスペースがない場合は再割り当てします。場所に再割り当てします。lengthしたがって、プロパティを設定して配列のサイズを変更することと、配列に追加してサイズを変更することを区別する必要はありません。

補遺: D を使用している人は、配列とスライスに関するこの記事を読む必要があります。それはそれらを非常によく説明しており、D で配列がどのように機能するかについてのより良いアイデアを提供するはずです。

于 2010-08-05T18:12:35.523 に答える
2

これを本格的な答えにしたくなかったのですが、前の答えについてはまだコメントできません。

連結と追加は少し異なる2つの操作だと思います。配列と要素で〜を使用すると、追加されます。2つの配列では、連結です。

代わりにこれを試すことができます:

a = a ~ 2;

そして、同じ結果が得られるかどうかを確認してください。

また、動作を定義したい場合は、.dup(または不変の場合は.idup)プロパティを使用するだけです。これは、参照の配列がある場合にも非常に役立ちます。メインアレイと.dupスライスを変更して、競合状態を気にせずに処理することができます。

編集:わかりました、私はそれを少し間違っていますが、とにかくあります。連結!=追加。

//最大

于 2010-08-05T19:01:53.780 に答える