Элементы массива по ссылке - баг или фича?

Статус
В этой теме нельзя размещать новые ответы.

no_alex

Новичок
Элементы массива по ссылке - баг или фича?

Наступил на очень неприятные грабли.... :(
Чтобы долго не расписывать суть, приведу пример простого тестового кода:
PHP:
<?php
class w
{
    private $a = array();
    private $b = array();

    public function init() {
        $this->a = array("x" => 1, "y" => 2, "z" => 3);

        foreach ($this->a as $k => $v) {
           $this->b[$k] =& $this->a[$k];
        }
    }

    public function get1() {
        return $this->a;
    }

    public function get2() {
        return $this->b;
    }

    public function view() {
        echo "<pre>";
        print_r($this->a);
        print_r($this->b);
        echo "</pre>";
    }
}

$o = new w();
$o->init();

$c1 = $o->get1();
$c1["x"] = 5;
$c2 = $o->get2();
$c2["y"] = 6;

$o->view();
?>
В результате выполнения этого кода мы получаем:
Код:
Array
(
    [x] => 5
    [y] => 6
    [z] => 3
)
Array
(
    [x] => 5
    [y] => 6
    [z] => 3
)
Обратите внимание на то, что методы get1() и get2() возвращают копии массивов, а не ссылки на них.
Но, не смотря на это, последующие изменения в "копиях" меняют и "оригинал". Причём, не важно с каким массивом я работаю с исходным или с клоном.

Я вначале подумал что это баг, но порассуждав немного - понял, что это "фича".

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

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


Единственное, что они убрали параметр allow_call_time_pass_reference. В результате вот такой код:
PHP:
<?php
function x($a) {
    $a = 2;
}
$b = 1;
x(&$b);
echo $b;
?>
выдает
Код:
Deprecated: Call-time pass-by-reference has been deprecated; if you would like to pass argument by reference, modify the declaration of x(). in test.php on line 6
, но все равно правильно отрабатывает (выдаст 2).

Так что же такого существенно изменилось в PHP6 при работе со ссылками? Или только ограничена передача ссылки снаружи в функцию?
 

zerkms

TDD infected
Команда форума
такая конструкция уже давно deprecated, и в рамках пхп - смысла не имеющая. а в остальном поведение вполне ожидаемое - ты вернул массив ссылок, потом по ссылкам модифицировал значение.
 

no_alex

Новичок
а в остальном поведение вполне ожидаемое
Я в этом не уверен. Если бы я вернул этот массив по ссылке, тогда да. А так, IMHO более правильным, было бы, когда я делаю копию массива - все элементы, рекурсивно, становятся копиями.
Ещё более странным для меня, было то, что свойство $this->a тоже превратилось в точно такой-же массив ссылок. Я всегда считал, что при выше приведённой процедуре исходный элемент не меняется, просто "клон"становится ссылкой на него.

такая конструкция уже давно deprecated
Да, но она все равно работает в 6-ом! Хотя говорили, что уберут её полностью. Разница только в том, что раньше сообщение о таком вызове я мог подавить флагом allow_call_time_pass_reference, а сейчас мне пришлось бы писать так: error_reporting = E_ALL & ~E_DEPRECATED.

С тем, что запрет на все предупреждения уровня E_DEPRECATED надо делать общими свойствами - я согласен! Но вот зачем они в 6-м оставили эту операцию, если она была deprecated еще в 5-м? Не понятно...

И второй вопрос - кто-нибудь ещё исследовал работу со ссылками в 6-м PHP? Потому как, я читал в анонсах, что там многое должно было измениться, но я не увидел всех этих изменений...
 

AmdY

Пью пиво
Команда форума
6-ка будет соответствовать 5.3, так что ссылки не должны сильно измениться. x(&$b); - а вот эту хрень вроде обещали запретить или я путаю с объектами по ссылке
 

Активист

Активист
Команда форума
x(&$b) - да, ее обещали запретить - без нее будет плохо :( Почему ее запрещают?) Вообще в c++ без нее никуда а в php - запрещают... странно.

Я как-то PHP месяца три назад обновил, у меня столько варнингов по этому поводу вылезло :)
 

zerkms

TDD infected
Команда форума
Активист
Вообще в c++ без нее никуда а в php - запрещают
потому что пхп это немного не с++ :))))) и в пхп x(&$b) абсурдна с точки зрения синтаксиса.
 

no_alex

Новичок
в пхп x(&$b) абсурдна с точки зрения синтаксиса.
Я бы так категорично не утверждал! У меня, например, тоже возникала необходимость использования такой конструкции.

Потом, когда я узнал что в PHP6 её вообще запретят, я нашёл как от неё избавиться, но PHP-код стал длиннее и менее понятен.
 

zerkms

TDD infected
Команда форума
Я бы так категорично не утверждал! У меня, например, тоже возникала необходимость использования такой конструкции.
в пхп синтаксис такой - что указание передачи переменной по ссылке содержится в декларации, а не вызове.

Потом, когда я узнал что в PHP6 её вообще запретят, я нашёл как от неё избавиться, но PHP-код стал длиннее и менее понятен.
эээээ

foo($bar);
function foo(& $var) {}

м? где код стал длиннее? я вижу что он только сократился, на 1 символ (в месте вызова)
 

no_alex

Новичок
Мне все-таки не понятна логика, по которой в моем первом примере видоизменятся исходный массив $this->a.

Рассмотрим ситуацию:
- я в базовом классе делаю свойством массив (и рассчитываю, что этот массив никак нельзя видоизменить извне);
- делаю метод, который может возвращать данный массив;
- кто-то делает наследника от исходного класса и у себя создает ссылки на элементы массива;
- в итоге, мой метод возвращает совсем не то, что я рассчитывал изначально;
- если теперь в возвращённом массиве изменить какие-то значения, то они поменяются и в свойствах объекта;
- в результате повторного обращения к объекту мы получим уже совсем не те значения.

Собственно, примерно такая ситуация у меня и произошла, когда я в самом начале писал, что "наступил на грабли".

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

Мне теперь что - перед тем как вернуть какой-то массив обязательно рекурсивно обходить все его элементы и делать из них копии? Бред!... :(
Я все больше склоняюсь к мысли, что это баг в PHP.

-~{}~ 09.11.08 14:43:

в пхп синтаксис такой - что указание передачи переменной по ссылке содержится в декларации, а не вызове.
Я это прекрасно понимаю! :)
Но поверьте мне - такая необходимость может возникнуть.
Мне просто не хочется это обсуждать в рамках этого топика. Я создавал его здесь, для другой цели.

-~{}~ 10.11.08 13:18:

Нашёл в мануале то место где чётко описано поведение PHP, в ситуациях аналогичных той что описана у меня:
http://php.net/manual/ru/language.references.whatdo.php

Замечание: $a и $b здесь абсолютно эквивалентны, но это не означает, что $a указывает на $b или наоборот. Это означает, что $a и $b указывают на одно место.
Замечание: При копировании массива ссылок, они не разыменовываются. Это также касается массивов, передаваемых функциям по значению.
Но все равно такое поведение IMHO, не всегда можно назвать правильным.
 

zerkms

TDD infected
Команда форума
раз уж ты тут приводишь аналогии с с++.
разве в с++ ссылки разыменуются? :)))
 

no_alex

Новичок
zerkms
Я вообще-то не приводил аналогии с C++. ;)

Не правильным я считаю то, что меняются свойства исходного массива.

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

В итоге полностью ломается логика работы класса.

Может такой подход был и правильным для процедурного программирования, но для OOP он не подходит!

-~{}~ 11.11.08 10:13:

Оказывается история этого бага тянется ещё с 2002 года:
http://bugs.php.net/bug.php?id=20993
 

zerkms

TDD infected
Команда форума
no_alex
а, спутал тебя с активистом, пардон.

он и возвращает копию массива, но он не разыменовывает ссылки.
 

no_alex

Новичок
Да я не против того, что он не разыменовывает ссылки. Хотя это тоже не всегда удобно, но тут все достаточно хорошо предсказуемо.

Я против того, что видоизменяется исходный массив! Из массива содержащего обычные элементы - он превращается в массив ссылок.
 

zerkms

TDD infected
Команда форума
no_alex
любая переменная ссылается на данные в памяти. оператор & создаёт вторую ссылку на ту же область. так что исходный массив как был массив ссылок, так им и остался. а второй массив - стал массивом с элементами, представляющими собой ссылки на те же области.
 

no_alex

Новичок
zerkms
Я рассматривал эту проблему не с точки зрения физического устройства, а с пользовательской. :)

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

zerkms

TDD infected
Команда форума
если ты не хочешь, чтобы изменения затрагивали первый массив, зачем ты составляешь второй из ссылок?
 

no_alex

Новичок
Объясняю. Это могу сделать не я, а кто-то.
Я создал это массив в базовом классе, а кто-то в "наследнике" выполнил подобные действия.

В результате нарушилась логика работы базового класса.
 

zerkms

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

-~{}~ 11.11.08 21:02:

опять же, тебя почему-то не возмущает возможность этого мифического "кого-то" изменения данных напрямую через $this->var['foo'] = 'bar';
а вот изменение этих же данных по ссылке (механизм, в принципе, тот же самый) тебя смущает. не понимаю.
 

HraKK

Мудак
Команда форума
если кто-то сделал ссылку на элементы массива, значит он понимал, что в итоге получится.
не верно.
no_alex
Именно поэтому везде трубиться что array не формат для передачи данных. Используйте object.
 
Статус
В этой теме нельзя размещать новые ответы.
Сверху