Игра со ссылками
Я понимаю, почему
$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;
$c['ID'] = 37;
echo $a['ID']."\n";
echo $b."\n";
echo $c['ID']."\n";
выходы 37, 42, 37
а
$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;
$b = 37;
echo $a['ID']."\n";
echo $b."\n";
echo $c['ID']."\n";
выходы 37, 37, 37
В обоих случаях $b
является ссылкой на $a['ID']
, а $c
является указателем на тот же объект, что и $a
.
Когда $b
изменяет $a['ID']
и $c['ID']
изменение, потому что назначение $b
изменяет значение, на которое ссылается $a['ID']
.
Когда $c['ID']
изменяется, новый int присваивается $a['ID']
, $b
больше не ссылается на $a['ID']
.
Но это меня мешает
$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;
$c['ID'] &= 0;
$c['ID'] |= 37;
echo $a['ID']."\n";
echo $b."\n";
echo $c['ID']."\n";
(выходы 37, 37, 37)
Это определенное поведение?
Я ничего не видел об этом в документации...
Ответы
Ответ 1
Возьмем этот код в качестве основы: (refcounting documentation)
$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');
Это дает:
a:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
public 'ID' => (refcount=2, is_ref=1),int 42
b:
(refcount=2, is_ref=1),int 42
c:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
public 'ID' => (refcount=2, is_ref=1),int 42
Как вы говорите:
$a
является объектом, $b
является ссылкой $a['ID']
($a['ID']
и $b
: refcount=2, is_ref=1
)
и $c является копией в качестве ссылки (с PHP5), поэтому $c является ссылкой $a (теперь это тот же объект: refcount=2, is_ref=0
)
Если мы делаем: $c['ID'] = 37;
Получаем:
a:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
public 'ID' => (refcount=1, is_ref=0),int 37
b:
(refcount=1, is_ref=0),int 42
c:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
public 'ID' => (refcount=1, is_ref=0),int 37
$c['ID']
присваивается новый int
so = >
$b
становится независимым (refcount=1
и is_ref=0
), а также $a['ID']
и $c['ID']
НО, поскольку $c
и $a
зависят, $a['ID']
и $c['ID']
принимают одно и то же значение 37.
Теперь возьмем базовый код, и мы сделаем следующее: $c['ID'] &= 0;
UPDATE:
Неожиданно получаем:
a:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
public 'ID' => (refcount=2, is_ref=1),int 0
b:
(refcount=2, is_ref=1),int 0
c:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
public 'ID' => (refcount=2, is_ref=1),int 0
вместо: (if: $c['ID'] = $c['ID'] & 0;
)
a:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
public 'ID' => (refcount=1, is_ref=0),int 0
b:
(refcount=1, is_ref=0),int 42
c:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
public 'ID' => (refcount=1, is_ref=0),int 0
ArrayObject реализует ArrayAccess так:
Как сказано в комментарии и описанном здесь:
Прямая модификация - это та, которая полностью заменяет значение размера массива, как в $obj [6] = 7. Косвенная модификация, с другой стороны, только изменяет часть измерения или пытается назначить измерение по ссылке на другую переменную, как в $obj [6] [7] = 7 или $var = & $OBJ [6]. Приращения c++ и сокращениями - также реализованы таким образом, что требуется косвенная модификация.
Возможный ответ:
"Комбинированный оператор (+ =, - =, & =, | =) можно было бы работать одинаково (косвенная модификация.)":
refcount
и is_ref
не влияют поэтому (в нашем случае) значения всех связанные переменные. ($c['ID']
= > $a['ID']
= > $b)
Ответ 2
Это более или менее определенное (но иногда недокументированное) поведение; главным образом потому, что $a
не является array
, а <<22 > .
Сначала взглянем на ваш третий фрагмент кода:
$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;
$c['ID'] &= 0;
Последнее назначение означает:
$tmp = &$c->offsetGet('ID');
$tmp &= 0; // or: $tmp = $tmp & 0;
Здесь вызывается только offsetGet()
, и она возвращает ссылку на $c['ID']
, как указано в этот комментарий. Поскольку offsetSet()
не вызывается, изменяется значение $b
.
Btw, приращение (++) и оператор декремента (-) работают аналогично, no offsetSet()
вызывается.
Различия
Это отличается от вашего первого примера:
$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;
$c['ID'] = 37;
Последний оператор имеет следующий эквивалент:
$c->offsetSet('ID', 37);
Прежде чем новое значение будет присвоено $c['ID']
, предыдущее значение будет эффективно unset()
; поэтому $b
- единственная переменная, все еще удерживающая до 42
.
Доказательство этого поведения можно увидеть, когда вы используете объекты вместо чисел:
class MyLoggerObj
{
public function __destruct()
{
echo "Destruct of " . __CLASS__ . "\n";
}
}
$a = new ArrayObject();
$a['ID'] = new MyLoggerObj();
$a['ID'] = 37;
echo $a['ID']."\n";
Вывод:
Destruct of MyLoggerObj
37
Как вы можете видеть, деструктор вызывается на MyLoggerObj
; потому что в этом случае нет переменной, которая больше удерживает значение.
Bonus
Если вы попытаетесь выяснить, когда offsetGet()
и offsetSet()
вызывается расширением ArrayObject
, вы будете разочарованы, узнав, что вы не можете правильно имитировать mixed &offsetGet($key);
. Фактически, это меняет поведение ArrayObject
таким образом, что невозможно доказать поведение этого класса.