Это - плохая практика для изменения динамических массивов, которые имеют ссылки на них?

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

От спецификации на массиве

Для максимизации эффективности время выполнения всегда пытается изменить размер массива на месте для предотвращения дополнительного копирования. Это будет всегда делать копию, если новый размер будет больше, и массив не был выделен через новый оператор, или предыдущее изменяют размер операции.

Так изменение длины neccesarily не повреждает ссылку:

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);

И то же при изменении длины или cancatenating (я ожидал бы это данное вышеупомянутое):

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?

8
задан simendsjo 5 August 2010 в 15:59
поделиться

2 ответа

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

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

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

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

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

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

EDIT: Оказалось, что документацию нужно обновить. Все, что может изменить размер массива, попытается сделать это на месте, если сможет (поэтому оно может не перераспределяться), но будет перераспределяться, если придется, чтобы не топтать память другого массива или если у него недостаточно места для перераспределения на месте. Таким образом, не должно быть никакого различия между изменением размера массива путем установки его свойства length и изменением размера путем добавления к нему.

ДОБАВЛЕНИЕ: Всем, кто использует D, стоит прочитать эту статью о массивах и срезах. Она довольно хорошо объясняет их, и должна дать вам гораздо лучшее представление о том, как массивы работают в D.

10
ответ дан 5 December 2019 в 13:59
поделиться

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

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

Вместо этого вы можете попробовать следующее:

a = a ~ 2;

И посмотреть, получите ли вы те же результаты.

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

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

// Макс

2
ответ дан 5 December 2019 в 13:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: