Это плохая практика, чтобы изменить динамические массивы, которые имеют ссылки на них? - PullRequest
8 голосов
/ 05 августа 2010

Я немного посмотрел на динамические массивы в 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]);

Из спецификации массива

Конкатенация всегда создает копию своих операндов, даже если один из операндов является массивом длины 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?

Ответы [ 2 ]

10 голосов
/ 05 августа 2010

В целом, вы, кажется, понимаете вещи довольно хорошо, но вы, похоже, неправильно понимаете цель свойства ptr. Он не указывает, ссылаются ли два массива на один и тот же экземпляр. Что он делает, так это дает вам указатель на то, что под ним находится массив C. Массив в D имеет свой length как часть этого, так что это больше похоже на структуру с длиной и указателем на массив C, чем на массив C. ptr позволяет получить массив C и передать его в код C или C ++. Вы, вероятно, не должны использовать его для чего-либо в чистом D-коде. Если вы хотите проверить, относятся ли две переменные массива к одному и тому же экземпляру, используйте оператор 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

Все, что ptr будет равным для двух массивов, указывает на то, что их первый элемент находится в том же месте в памяти. В частности, их length могут отличаться. Однако это означает, что любые перекрывающиеся элементы будут изменены в обоих массивах, если вы измените их в одном из них.

При изменении length массива D пытается избежать перераспределения, но может принять решение о перераспределении, поэтому вы не можете полностью полагаться на то, будет ли он перераспределяться или нет. Например, он будет перераспределен, если не сделает этого, будет топать память другого массива (включая те, которые имеют то же значение для ptr). Он также может перераспределить, если недостаточно памяти для изменения размера на месте. По сути, он будет перераспределен, если не сделает этого, помешает памяти другого массива, и может перераспределить или не перераспределить иначе. Так что, как правило, не стоит полагаться на то, будет ли массив перераспределяться или нет, когда вы устанавливаете его length.

Я бы ожидал, что добавление всегда будет копировать в соответствии с документами, но в соответствии с вашими тестами оно действует так же, как length (я не знаю, означает ли это, что документы должны быть обновлены или это ошибка - я думаю, что документы должны быть обновлены). В любом случае вы, безусловно, не можете полагаться на другие ссылки на этот массив, чтобы все еще ссылаться на тот же массив после добавления.

Что касается срезов, они работают так, как ожидалось, и широко используются в D, особенно в стандартной библиотеке Phobos. Срез - это диапазон для массива, а диапазоны - основная концепция Фобоса. Однако, как и многие другие диапазоны, изменение контейнера, для которого предназначен диапазон / срез, может сделать этот диапазон / срез недействительным. Вот почему, когда вы используете функции, которые могут изменять размеры контейнеров в Фобосе, вам нужно использовать функции с предваряющей стабильной (например, stableRemove() или stableInsert()), если вы не хотите рисковать аннулированием диапазонов, которые у вас есть к этому контейнер.

Кроме того, срез - это массив, аналогичный массиву, на который он указывает. Поэтому, естественно, изменение его length или добавление к нему будет следовать всем тем же правилам, что и для изменения length или добавлению к любому другому массиву, и поэтому он может быть перераспределен и больше не будет срезом в другой массив.

В значительной степени, вам просто нужно знать, что изменение length массива любым способом может привести к перераспределению, поэтому вам следует избегать этого, если вы хотите, чтобы ссылки продолжали ссылаться на тот же экземпляр массива , И если вам абсолютно необходимо убедиться, что они не указывают на одну и ту же ссылку, то вам нужно использовать dup, чтобы получить новую копию массива. Если вы вообще не связываетесь с length массива, то ссылки на массивы (будь то срезы или ссылки на весь массив) будут продолжать счастливо ссылаться на тот же массив.

РЕДАКТИРОВАТЬ: Оказывается, документы должны быть обновлены.Все, что может изменить размер массива, попытается сделать это на месте, если оно может (так что он может не перераспределить), но будет перераспределять, если это необходимо, чтобы избежать растоптания в памяти другого массива или если у него недостаточно местаперераспределить на месте.Таким образом, не должно быть никакого различия между изменением размера массива путем установки его свойства length и изменением его размера путем добавления к нему.

ADDENDUM: Любой, кто использует D, действительно должен прочитать эта статья о массивах и срезах.Это объясняет их довольно хорошо и должно дать вам гораздо лучшее представление о том, как работают массивы в D.

2 голосов
/ 05 августа 2010

Я действительно не хотел превращать это в полномасштабный ответ, но пока не могу комментировать предыдущий ответ.

Я думаю, что объединение и добавление - это две немного разные операции.Если вы используете ~ с массивом и элементом, он добавляется;с двумя массивами это конкатенация.

Вы можете попробовать это вместо этого:

a = a ~ 2;

и посмотреть, если вы получите те же результаты.определенное поведение, просто используйте свойства .dup (или .idup для неизменяемых).Это также очень полезно, если у вас есть массив ссылок;Вы можете модифицировать основной массив и срезы .dup, чтобы работать, не беспокоясь о состоянии гонки.

РЕДАКТИРОВАТЬ: хорошо, я немного ошибся, но это все равно.Объединение! = Добавление.

// Макс.

...