Не уверен, насколько этот сценарий inout безопасен, потому что массив становится недействительным по возвращении
Приведенный ниже код не дает сбоя, но я не могу объяснить почему, учитывая "ограниченные" доступные документы.
func foo(inout a: [Int], inout b: Int) {
a = []
b = 99
}
var arr = [1,2,3]
// confusion: where does the "out" of b go to?
// storage for a was already cleared / invalidated
foo(&arr, b: &arr[2])
print(arr) // arr is empty
1 ответ
Я верю, что это то, что происходит. Когда вы назначаете
a = []
ты указываешь a
в новый массив. Исходный массив все еще существует в памяти, и когда вы делаете:
b = 99
вы модифицируете исходный массив, а не новый массив, который a
Рекомендации.
Какие у вас есть доказательства того, что это так?
Рассмотрите эту модификацию в своем эксперименте:
Случай 1:
func foo(inout a: [Int], inout b: Int) {
a[0] = 4
a[1] = 5
a[2] = 6
b = 99
}
var arr = [1,2,3]
foo(&arr, b: &arr[2])
print(arr) // prints "[4, 5, 99]"
Теперь рассмотрим это:
Случай 2:
func foo(inout a: [Int], inout b: Int) {
a = [4, 5, 6]
b = 99
}
var arr = [1,2,3]
foo(&arr, b: &arr[2])
print(arr) // prints "[4, 5, 6]"
Очевидно, что изменение отдельных элементов a
это не то же самое, что присвоение массива a
,
В случае 1 мы изменили исходный массив, превратив элементы в 4
, 5
, а также 6
и назначение b
изменено a[2]
как и ожидалось.
В случае 2 мы назначили [4, 5, 6]
в a
который не изменил исходные значения на 4
, 5
, а также 6
но вместо этого указал a
в новый массив. Назначение b
не меняется a[2]
в этом случае, потому что a
теперь указывает на новый массив в другом месте в памяти.
Случай 3:
func foo(inout a: [Int], inout b: Int) {
let a1 = a
a = [4, 5, 6]
b = 99
print(a) // prints "[4, 5, 6]"
print(a1) // prints "[1, 2, 99]"
}
var arr = [1,2,3]
foo(&arr, b: &arr[2])
print(arr) // prints "[4, 5, 6]"
В случае 3 мы можем присвоить исходный массив a1
перед назначением нового массива a
, Это дает нам имя для исходного массива. когда b
назначен, a1[2]
модифицируется.
Из комментариев:
Ваш ответ объясняет, почему назначение b внутри функции работает. Однако, когда foo заканчивается и копирует входные переменные обратно, в этот момент я не вижу, как swift знает, как отложить освобождение исходного массива до назначения &[2].
Вероятно, это результат подсчета ссылок со стороны ARC. Исходный массив передается по ссылке на foo
и счетчик ссылок увеличивается. Исходный массив не освобождается, пока счетчик ссылок не будет уменьшен в конце foo
,
Это кажется таким же волосатым, как то, что документы уже запрещают - передавая одну и ту же переменную дважды как inout. Также ваш случай3 удивителен. Разве let a1 = a line не должна делать семантику struct / value и копировать снимок массива прямо сейчас?
Да. Я согласен, что Случай 3 удивителен, но он действительно раскрывает кое-что из того, что происходит под одеялом. Обычно, когда вы присваиваете один массив новой переменной, Swift не сразу делает копию. Вместо этого он просто указывает второй массив на первый и счетчик ссылок увеличивается. Это сделано ради эффективности. Копировать необходимо только при изменении одного из массивов. В этом случае, когда a
модифицируется, a1
сохраняет оригинальную копию массива в памяти.
Это действительно сбивает с толку; Я не понимаю, почему a1 не получит 1,2,3. Также пусть должен быть неизменным!
Дело в том, что a1
модифицируется когда b
установлен показывает, что a1
указывает на память исходного массива. Свифт, видимо, не рассматривает настройку b
как изменение a1
, Возможно, потому что он уже убедился a
был изменчив, когда foo
был вызван с &a[2]
,