Молодой сотрудник, который изучал ОО, спросил меня, почему каждый объект передается по ссылке, что противоположно примитивным типам или структурам. Это общая характеристика языков, таких как Java и C #.
Я не мог найти хороший ответ для него.
Каковы мотивы для этого дизайнерского решения? Разработчики этих языков устали от необходимости каждый раз создавать указатели и определения типов?
programming-languages
object-oriented
Густаво Кардосо
источник
источник
Ответы:
Основные причины сводятся к этому:
Отсюда и ссылки.
источник
Простой ответ:
Минимизация потребления памяти
и
процессорного времени при воссоздании и выполнении глубокой копии каждого переданного объекта.
источник
В C ++ у вас есть два основных варианта: возврат по значению или возврат по указателю. Давайте посмотрим на первый:
Предполагая, что ваш компилятор не достаточно умен, чтобы использовать оптимизацию возвращаемого значения, что происходит здесь так:
Мы сделали копию объекта бессмысленно. Это пустая трата времени на обработку.
Давайте вместо этого посмотрим на возврат по указателю:
Мы удалили избыточную копию, но теперь мы представили другую проблему: мы создали объект в куче, который не будет автоматически уничтожен. Мы должны разобраться с этим сами:
Знание того, кто несет ответственность за удаление объекта, выделенного таким образом, - это то, что может быть сообщено только комментариями или соглашением. Это легко приводит к утечкам памяти.
Для решения этих двух проблем было предложено множество обходных путей - оптимизация возвращаемого значения (при которой компилятор достаточно умен, чтобы не создавать избыточную копию при возврате по значению), передавая ссылку на метод (поэтому функция вставляет в существующий объект, а не создание нового), умные указатели (так что вопрос о праве собственности является спорным).
Создатели Java / C # поняли, что всегда возвращать объект по ссылке - лучшее решение, особенно если язык поддерживает его изначально. Он связан со многими другими функциями, такими как сборка мусора и т. Д.
источник
Многие другие ответы имеют хорошую информацию. Я хотел бы добавить один важный момент о клонировании, который был затронут лишь частично.
Использование ссылок разумно. Копировать вещи опасно.
Как говорили другие, в Java нет естественного «клона». Это не просто отсутствующая функция. Вы никогда не захотите просто копировать (волевым или глубоким) каждое свойство объекта. Что если это свойство было подключением к базе данных? Вы не можете просто «клонировать» соединение с базой данных больше, чем вы можете клонировать человека. инициализация существует по причине.
Глубокие копии - это отдельная проблема. Насколько глубоки вы на самом деле ? Вы определенно не можете скопировать что-либо статичное (включая любые
Class
объекты).Таким образом, по той же причине, по которой нет естественного клона, объекты, которые передаются как копии, создают безумие . Даже если бы вы могли «клонировать» соединение с БД - как бы вы теперь обеспечивали его закрытие?
* См. Комментарии. Под этим утверждением «никогда» я подразумеваю авто-клон, который клонирует каждое свойство. Java не предоставил его, и, вероятно, вам, как пользователю языка, не стоит создавать свой собственный язык по причинам, перечисленным здесь. Клонирование только непереходных полей было бы началом, но даже в этом случае вы должны быть внимательны при определении того,
transient
где это уместно.источник
clone
в порядке. Под «волей-неволей» я подразумеваю копирование всех свойств, не думая об этом - без целенаправленных намерений. Разработчики языка Java вынудили это сделать, потребовав пользовательскую реализациюclone
.На объекты всегда ссылаются в Java. Они никогда не передаются вокруг себя.
Одним из преимуществ является то, что это упрощает язык. Объект C ++ может быть представлен как значение или ссылка, что создает необходимость использования двух разных операторов для доступа к члену:
.
и->
. (Есть причины, по которым это не может быть консолидировано; например, умные указатели - это значения, которые являются ссылками и должны сохранять их различимыми.) Java нуждается только в.
.Другая причина заключается в том, что полиморфизм должен быть сделан по ссылке, а не по значению; объект, обработанный значением, только там, и имеет фиксированный тип. Это возможно испортить в C ++.
Кроме того, Java может переключать назначение по умолчанию / копировать / что угодно. В C ++ это более или менее глубокая копия, в то время как в Java это простое назначение / копирование / что угодно указателя, с
.clone()
и тому подобное на случай, если вам нужно скопировать.источник
const
его значение не может быть изменено, чтобы оно указывало на другие объекты. Ссылка - это другое имя для объекта, оно не может быть NULL и не может быть пересоздано. Обычно это реализуется простым использованием указателей, но это деталь реализации.Ваше первоначальное утверждение о том, что объекты C # передаются по ссылке, неверно. В C # объекты являются ссылочными типами, но по умолчанию они передаются по значению, как и типы значений. В случае ссылочного типа «значение», которое копируется как параметр метода передачи по значению, является самой ссылкой, поэтому изменения свойств внутри метода будут отражены за пределами области действия метода.
Однако, если бы вам пришлось переназначить саму переменную параметра внутри метода, вы увидите, что это изменение не отражается за пределами области действия метода. Напротив, если вы на самом деле передаете параметр по ссылке, используя
ref
ключевое слово, это поведение работает должным образом.источник
Быстрый ответ
Дизайнеры Java и подобных языков хотели применить концепцию «все является объектом». И передача данных в качестве ссылки очень быстро и не занимает много памяти.
Дополнительный расширенный скучный комментарий
Хотя эти языки используют ссылки на объекты (Java, Delphi, C #, VB.NET, Vala, Scala, PHP), правда в том, что ссылки на объекты являются замаскированными указателями на объекты. Нулевое значение, выделение памяти, копия ссылки без копирования всех данных объекта, все они являются указателями на объекты, а не на простые объекты !!!
В Object Pascal (не Delphi) и C ++ (не Java, не C #) объект может быть объявлен как статическая распределенная переменная, а также с динамически распределенной переменной с помощью указателя («ссылка на объект» без »). сахарный синтаксис "). В каждом случае используется определенный синтаксис, и нет способа запутаться, как в Java «и друзья». В этих языках объект может передаваться как значение или как ссылка.
Программист знает, когда требуется синтаксис указателя, а когда нет, но в Java и других языках это сбивает с толку.
До того, как Java существовала или стала мейнстримом, многие программисты изучали ОО на С ++ без указателей, передавая по значению или по ссылке, когда это необходимо. При переходе от обучения к бизнес-приложениям они обычно используют указатели объектов. Библиотека QT является хорошим примером этого.
Когда я изучал Java, я пытался понять, что все является объектной концепцией, но запутался в кодировании. В конце концов, я сказал: «Хорошо, это объекты, динамически размещаемые с указателем с синтаксисом статически размещенного объекта», и у меня снова не возникло проблем с кодированием.
источник
Java и C # получают контроль над памятью низкого уровня от вас. «Куча», в которой находятся создаваемые вами объекты, живет своей жизнью; например, сборщик мусора собирает объекты всякий раз, когда он предпочитает.
Поскольку между вашей программой и этой «кучей» существует отдельный уровень косвенности, два способа обращения к объекту по значению и по указателю (как в C ++) становятся неразличимыми : вы всегда обращаетесь к объектам «по указателю» на где-то в куче. Вот почему такой подход к проектированию делает передачу по ссылке семантикой присваивания по умолчанию. Java, C #, Ruby и так далее.
Вышесказанное касается только императивных языков. В языках , упомянутых выше контроля над памятью передаются во время выполнения, но язык дизайн также говорит : «Эй, но на самом деле, там есть память, и там есть объекты, и они делают занимают память». Функциональные языки абстрагируются еще дальше, исключая понятие «память» из их определения. Вот почему передача по ссылке не обязательно относится ко всем языкам, где вы не контролируете низкоуровневую память.
источник
Я могу придумать несколько причин:
Копирование примитивных типов тривиально, обычно оно переводится в одну машинную инструкцию.
Копирование объектов не является тривиальным, объект может содержать элементы, которые сами являются объектами. Копирование объектов требует много времени и ресурсов процессора. Есть даже несколько способов копирования объекта в зависимости от контекста.
Передача объектов по ссылке обходится дешево, а также становится удобной, когда вы хотите поделиться / обновить информацию об объекте между несколькими клиентами объекта.
Сложные структуры данных (особенно те, которые являются рекурсивными) требуют указателей. Передача объектов по ссылке - это просто более безопасный способ передачи указателей.
источник
Потому что в противном случае функция должна иметь возможность автоматически создавать (очевидно глубокую) копию любого типа объекта, который передается ей. И обычно это невозможно угадать, чтобы сделать это. Таким образом, вам придется определить реализацию метода copy / clone для всех ваших объектов / классов.
источник
Потому что Java была разработана как лучший C ++, а C # была разработана как лучший Java, и разработчики этих языков устали от принципиально сломанной объектной модели C ++, в которой объекты являются типами значений.
Два из трех фундаментальных принципов объектно-ориентированного программирования - это наследование и полиморфизм, и трактовка объектов как типов значений вместо ссылочных типов приводит к хаосу в обоих случаях. Когда вы передаете объект в функцию в качестве параметра, компилятор должен знать, сколько байтов нужно передать. Когда ваш объект является ссылочным типом, ответ прост: размер указателя одинаков для всех объектов. Но когда ваш объект является типом значения, он должен передать фактический размер значения. Так как производный класс может добавлять новые поля, это означает sizeof (производный)! = Sizeof (base), и полиморфизм выходит за пределы окна.
Вот тривиальная программа C ++, которая демонстрирует проблему:
Вывод этой программы не тот, который был бы для эквивалентной программы на любом нормальном языке OO, потому что вы не можете передать производный объект по значению функции, ожидающей базовый объект, поэтому компилятор создает конструктор скрытого копирования и передает копия родительской части объекта Child , вместо передачи объекта Child, как вы сказали. Скрытые семантические ошибки, подобные этой, являются причиной того, что в C ++ следует избегать передачи объектов по значению, и это невозможно вообще почти во всех других языках ОО.
источник
Потому что иначе не было бы полиморфизма.
В ОО-программировании вы можете создать больший
Derived
класс из классаBase
, а затем передать его функциям, ожидающимBase
единицу. Довольно тривиально, а?За исключением того, что размер аргумента функции фиксирован и определяется во время компиляции. Вы можете утверждать, что хотите, исполняемый код похож на это, и языки должны выполняться в тот или иной момент (чисто интерпретируемые языки этим не ограничиваются ...)
Теперь существует один фрагмент данных, который четко определен на компьютере: адрес ячейки памяти, обычно выражаемый в виде одного или двух «слов». Это видно как указатели или ссылки в языках программирования.
Таким образом, для передачи объектов произвольной длины наиболее простой способ - передать указатель / ссылку на этот объект.
Это техническое ограничение ОО-программирования.
Но поскольку для больших типов вы, как правило, предпочитаете передавать ссылки, чтобы избежать копирования, это обычно не считается серьезным ударом :)
Однако есть одно важное следствие того, что в Java или C # при передаче объекта в метод вы не представляете, будет ли ваш объект изменен методом или нет. Это затрудняет отладку / распараллеливание, и это проблема, которую пытаются решить функциональные языки и прозрачная референция -> копирование не так уж и плохо (когда это имеет смысл).
источник
Ответ в названии (ну почти в любом случае). Ссылка (например, адрес) просто ссылается на что-то другое, значение - это еще одна копия чего-то еще. Я уверен, что кто-то, вероятно, упомянул что-то для следующего эффекта, но будут обстоятельства, при которых подходит один, а не другой (Безопасность памяти против Эффективности памяти). Все дело в управлении памятью, памятью, памятью ... ПАМЯТЬ! : D
источник
Хорошо, я не говорю, что это именно то, почему объекты являются ссылочными типами или передаются по ссылке, но я могу привести вам пример того, почему это очень хорошая идея в долгосрочной перспективе.
Если я не ошибаюсь, когда вы наследуете класс в C ++, все методы и свойства этого класса физически копируются в дочерний класс. Это было бы как писать содержимое этого класса снова внутри дочернего класса.
Таким образом, это означает, что общий размер данных в вашем дочернем классе является комбинацией материала в родительском классе и производном классе.
EG: #include
Который покажет вам:
Это означает, что если у вас есть большая иерархия из нескольких классов, общий размер объекта, как заявлено здесь, будет комбинацией всех этих классов. Очевидно, что эти объекты были бы значительно большими во многих случаях.
Решение, я считаю, состоит в том, чтобы создать его в куче и использовать указатели. Это означает, что размер объектов классов с несколькими родителями будет в некотором смысле управляемым.
Вот почему использование ссылок было бы более предпочтительным методом для этого.
источник