Как вернуть ссылку на объект c

от admin

Как вернуть ссылку на объект c

Кроме параметров метода, которые с помощью модификатора ref позволяют передавать значение по ссылке, C# также позволяет с помощью ключевого слова ref возвращать ссылку из метода и определять переменную, которая будет хранить ссылку.

Переменная-ссылка

Для определения локальной переменной-ссылки (ref local) перед ее типом ставится ключевое слово ref :

Здесь переменная xRef указывает не просто на значение переменной x, а на область в памяти, где располагается эта переменная. Для этого перед x также указывается ref.

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

Получив ссылку, мы можем манипулировать значением по этой ссылке. Например:

Ссылка как результат функции

Для возвращения из функции ссылки в сигнатуре функции перед возвращаемым типом, а также после оператора return следует указать ключевое слово ref :

В методе Find ищем число в массиве, но вместо самого значения числа возвращаем ссылку на него в памяти. Для этого в сигнатуре метода в качестве типа результата функции указывается не просто int , а ref int .

Кроме того, в самом методе после слова return также ставится ref :

Тем самым мы получаем не просто значение, а ссылку на объект в памяти.

В методе Main для определения переменной, которая будет содержать ссылку, используется ключевое слово ref. При вызове метода также указывается слово ref:

В итоге переменная numberRef будет содержать ссылку на объект int, и через данную переменную в последствиии мы можем изменить объект по этой ссылке.

Другой пример — возвращение ссылки на максимальное число из двух:

Стоит обратить внимание, что параметры метода в этом случае определены с ключевым словом ref.

При определении метода, который возвращает ссылку, следует учитывать, что такой метод естественно не может иметь тип void. Кроме того, такой метод не может возвращать:

11.5 – Возвращение значений по значению, по ссылке и по адресу

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

Как оказалось, возврат вызывающему значений из функции по значению, по адресу или по ссылке работает почти так же, как передача аргументов в функцию. У каждого метода присутствуют все те же плюсы и минусы. Основное различие между ними состоит в том, что направление потока данных меняется на противоположное. Однако есть еще одна дополнительная сложность – поскольку локальные переменные в функции выходят за пределы области видимости и уничтожаются при возврате из функции, нам необходимо учитывать влияние этого на каждый тип возврата.

Возврат по значению

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

Еще одно преимущество возврата по значению заключается в том, что вы можете возвращать переменные (или выражения), которые включают в себя локальные переменные, объявленные внутри функции, не беспокоясь о проблемах с областью видимости. Поскольку переменные вычисляются до возврата из функции, и вызывающему возвращается копия значения, не возникает проблем, когда переменная функции выходит за пределы области видимости в конце функции.

Возврат по значению является наиболее подходящим при возврате переменных, которые были объявлены внутри функции, или для возврата аргументов функции, переданных по значению. Однако, как и передача по значению, возврат по значению для структур и больших классов выполняется медленно.

Когда использовать возврат по значению:

  • При возврате переменных, которые были объявлены внутри функции
  • При возврате аргументов функции, переданных по значению

Когда не использовать возврат по значению:

  • При возврате встроенного массива или указателя (используйте возврат по адресу)
  • При возврате большой структуры или класса (используйте возврат по ссылке)

Возврат по адресу

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

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

Как вы можете видеть здесь, value уничтожается сразу после того, как его адрес возвращается вызывающему. Конечным результатом является то, что вызывающий получает адрес невыделенной памяти (висячий указатель), что вызовет проблемы при использовании. Это частая ошибка, которую допускают начинающие программисты. Многие более новые компиляторы выдают предупреждение (а не ошибку), если программист пытается вернуть локальную переменную по адресу – однако есть довольно много способов обмануть компилятор, чтобы он позволил вам сделать что-то нелегальное без генерации предупреждения, поэтому эта ответственность лежит на программисте, чтобы гарантировать, что указатель, который он возвращает, после возврата из функции будет указывать на допустимую переменную.

Возврат по адресу часто использовался для возврата вызывающему динамически выделенной памяти:

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

Когда использовать возврат по адресу:

  • При возврате динамически выделенной памяти, когда вы не можете использовать тип, который обрабатывает выделение памяти за вас.
  • При возврате аргументов функции, переданных по адресу.

Когда не использовать возврат по адресу:

  • При возврате переменных, которые были объявлены внутри функции, или параметров, переданных по значению (используйте возврат по значению).
  • При возврате большой структуры или класса, переданного по ссылке (используйте возврат по ссылке)

Возврат по ссылке

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

Однако, как и при возврате по адресу, вы не должны возвращать по ссылке локальные переменные. Рассмотрим следующий пример:

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

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

Когда мы вызываем getElement(array, 10) , getElement() возвращает ссылку на элемент массива с индексом 10. Затем main() использует эту ссылку для присвоения этому элементу значения 5.

Хотя это в некоторой степени надуманный пример (потому что вы можете получить доступ к array[10] напрямую), как только вы узнаете о классах, вы найдете гораздо больше применений для возврата значений по ссылке.

Когда использовать возврат по ссылке:

  • При возврате параметра-ссылки.
  • При возврате члена объекта, который был передан в функцию по ссылке или адресу.
  • При возврате большой структуры или класса, который не будет уничтожен в конце функции (например, тот, который был передан по ссылке).

Когда не использовать возврат по ссылке:

  • При возврате переменных, которые были объявлены внутри функции, или параметров, переданных по значению (используйте возврат по значению).
  • При возврате значения встроенного массива или указателя (используйте возврат по адресу).

Смешивание возвращаемых ссылок и значений

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

В случае A мы присваиваем возвращаемое значение-ссылку переменной-нессылке. Поскольку giana не является ссылкой, возвращаемое значение копируется в giana , как если бы returnByReference() выполняла возврат по значению.

В случае B мы пытаемся инициализировать ссылку ref копией значения, возвращаемого функцией returnByValue() . Однако, поскольку возвращаемое значение не имеет адреса (это r-значение), это вызовет ошибку компиляции.

В случае C мы пытаемся инициализировать константную ссылку cref копией значения, возвращаемого returnByValue() . Поскольку константные ссылки могут связываться с r-значениями, здесь нет проблем. Обычно срок действия r-значения истекает в конце выражения, в котором оно создано, однако, когда оно привязывается к константной ссылке, время жизни r-значения (в данном случае возвращаемого значения функции) увеличивается до времени жизни ссылки (в данном случае cref )

Продление времени жизни не сохраняет висячие ссылки

Рассмотрим следующую программу:

В приведенной выше программе returnByReference() возвращает константную ссылку на значение, которое выйдет за пределы области видимости при завершении функции. Обычно это недопустимо, так как это приведет к висячей ссылке. Однако мы также знаем, что присвоение значения константной ссылке может продлить время жизни этого значения. Итак, что здесь имеет приоритет? Что раньше, 5 выходит из области видимости, или ref продлевает срок жизни 5?

Ответ заключается в том, что сначала 5 выходит за пределы области видимости, затем ссылка на 5 копируется обратно вызывающему, а затем ref продлевает время жизни теперь уже висячей ссылки.

Однако следующий код работает должным образом:

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

Возврат нескольких значений

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

К счастью, есть несколько косвенных методов, которые можно использовать.

Как описано в уроке «11.3 – Передача аргументов по ссылке», выходные параметры предоставляют первый метод для передачи нескольких значений данных обратно вызывающему. Мы не рекомендуем этот метод.

Второй метод предполагает использование структуры только для данных:

Третий способ (представленный в C++11) — использовать std::tuple . Кортеж (tuple) – это последовательность элементов, которые могут быть разных типов, где тип каждого элемента должен быть указан явно.

Читать:
Как найти диагональ ромба зная периметр и угол

Вот пример, который возвращает кортеж и использует std::get для получения n-го элемента кортежа:

Это работает идентично предыдущему примеру.

Вы также можете использовать std::tie для распаковки кортежа в предопределенные переменные, например:

Начиная с C++17, для упрощения разделения нескольких возвращаемых значений на отдельные переменные может использоваться объявление структурированной привязки:

Использование структуры – лучший вариант, чем кортеж, если вы используете эту структуру в нескольких местах. Однако для случаев, когда вы просто упаковываете эти значения для возврата, и не будет повторного использования при определении новой структуры, кортеж немного чище, поскольку он не вводит новый пользовательский тип данных.

Заключение

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

Небольшой тест

Напишите прототипы для каждой из следующих функций. Используйте наиболее подходящие параметры и возвращаемые типы (по значению, по адресу или по ссылке), включая использование const , где это необходимо.

1) Функция с именем sumTo() , которая принимает параметр int и возвращает сумму всех чисел между предыдущими значениями и переданным входным числом.

2) Функция с именем printEmployeeName() , которая принимает в качестве входных данных структуру Employee .

3) Функция с именем minmax() , которая принимает на вход два числа int и возвращает вызывающему меньшее и большее числа в std::pair . std::pair работает так же, как std::tuple , но хранит ровно два элемента.

4) Функция с именем getIndexOfLargestValue() , которая принимает массив значений int (как std::vector ) и возвращает индекс самого большого элемента в массиве.

5) Функция с именем getElement() , которая принимает массив из std::string (как std::vector ) и индекс и возвращает элемент массива по этому индексу (не копию). Предположим, что индекс действителен, а возвращаемое значение – const .

Как работает возврат ссылки?

Что-то я туплю. Есть класс буфера, который, возвращая элемент, копирует его в свой член OutObject и возвращает ссылку на OutObject. Принимается же этот элемент в объект:

К какой области памяти теперь относится data? К той же, что и раньше? Или к OutObject? Или здесь все же происходит копирование значения?

о боже, ответ получился безумно длинным и без BBcode выглядит ужасно нечитабельно :((

не пугайтесь огромного размера :))

предисловие

что такое ссылка ?

все ссылки это по сути константные указатели (не путать с указателем на конст)

у ссылок есть пара отличий от просто указателя

  1. нельзя менять адрес_объекта
  2. адрес_объекта не может быть равным нулю
  3. тип ссылки и объекта должен быть одинаковым
  4. адрес_объекта можно назначить только во время создания ссылки

во время компиляции, компилятор автоматически переделает ссылку в конст_переменная_указатель , возьмёт адрес_объекта и во всех местах где используется ссылка подставит разыменование ( * конст_переменная_указатель ).

передача\возврат значений в\из функцию

главное всегда помнить в функцию ничего не передаётся и функция ничего не возвращает

на самом деле вы создаёте в функции новую переменную и присваиваете ей значение из вызвавшей_функции (эта переменная может быть указателем, ссылкой или объектом)

при возврате вы создаёте в вызвавшей_функции новую переменную и присваиваете ей значение из функции (эта переменная так же может быть указателем, ссылкой или объектом. Она имеет адрес в памяти но не имеет имени)

создаём в функции новые переменные

—————пример1:

при вызове fun() в месте $ будет выполнена операция

—————пример2:

при вызове fun() в месте $ будет выполнена операция

—————пример3: (работать не будет)

при вызове fun() в месте $ будет выполнена операция

создаём в вызвавшей_функции переменные без имени «^»

—————пример1:

в вызвавшей_функции в месте $ будет выполнена операция

—————пример2:

в вызвавшей_функции в месте $ будет выполнена операция

—————пример3: (работать не будет)

в вызвавшей_функции в месте $ будет выполнена операция

смешиваем

—————пример:

в функции_fun в месте $1 будет выполнена операция

затем в вызвавшей_функции в месте $2 будет выполнена операция

тоесть ^_ссылка и переменная_1_ссылка будут указывать на переменная_объект

если кто не понял привожу аналогичный код в виде указателей

в функции_fun в месте $1 будет выполнена операция

затем в вызвавшей_функции в месте $2 будет выполнена операция

p.s стоит ли писать про то когда вызывается конструктор_копирования , а когда оператор= .

ref locals и ref returns в C#: подводные камни производительности

В языке C# с самого начала поддерживалась передача аргументов по значению или по ссылке. Но до версии 7 компилятор C# поддерживал только один способ возврата значения из метода (или свойства) — возврат по значению. В C# 7 ситуация изменилась с введением двух новых возможностей: ref returns и ref locals. Подробнее о них и об их производительности — под катом.

Причины

Между массивами и другими коллекциями существует множество различий с точки зрения среды CLR. Среда CLR с самого начала поддерживала массивы, и их можно рассматривать как встроенный функционал. Среда CLR и JIT-компилятор умеют работать с массивами, а также у них есть еще одна особенность: индексатор массива возвращает элементы по ссылке, а не по значению.

Чтобы продемонстрировать это, нам придется обратиться к запретному методу — воспользоваться изменяемым (mutable) типом значения:

Тестирование пройдет успешно, потому что индексатор массива значительно отличается от индексатора List.

Компилятор C# дает специальную инструкцию индексатору массивов — ldelema, которая возвращает управляемую ссылку на элемент данного массива. По сути, индексатор массива возвращает элемент по ссылке. Однако List не может вести себя таким же образом, потому что в C# было невозможно* вернуть псевдоним внутреннего состояния. Поэтому индексатор List возвращает элемент по значению, то есть возвращает копию данного элемента.

*Как мы скоро увидим, индексатор List по-прежнему не может возвращать элемент по ссылке.

Это значит, что ma[0].IncrementX() вызывает метод, изменяющий первый элемент массива, в то время как ml[0].IncrementX() вызывает метод, изменяющий копию элемента, не затрагивая исходный список.

Возвращаемые ссылочные значения и ссылочные локальные переменные: основы

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

1. Простой пример:

2. Возвращаемые ссылочные значения и модификатор readonly

Возвращаемое ссылочное значение может вернуть псевдоним поля экземпляра, а начиная с C# версии 7.2, можно возвращать псевдоним без возможности записи в соответствующий объект, используя модификатор ref readonly:

  • Методы и свойства могут возвращать «псевдоним» внутреннего состояния. Для свойства в этом случае не должен быть определен метод задания.
  • Возврат по ссылке разрывает инкапсуляцию, так как клиент получает полный контроль над внутренним состоянием объекта.
  • Возврат с помощью ссылки только для чтения позволяет избежать излишнего копирования типов значений, при этом не разрешая клиенту изменять внутреннее состояние.
  • Ссылки только для чтения можно использовать для ссылочных типов, хотя это и не имеет особого смысла при нестандартных случаях.
  • Невозможно вернуть ссылку на локальную переменную.
  • Невозможно вернуть ссылку на this в структурах.
  • Можно вернуть ссылку на переменную, размещенную в куче (например, на член класса).
  • Можно вернуть ссылку на параметры ref/out.

Теперь, когда мы получили общее представление о возвращаемых ссылочных значениях и ссылочных локальных переменных, давайте рассмотрим, как их можно использовать.

Использование возвращаемых ссылочных значений в индексаторах

Чтобы проверить влияние этих функций на производительность, мы создадим уникальную неизменяемую коллекцию по названием NaiveImmutableList<Т> и сравним ее с T[] и List для структур разного размера (4, 16, 32 и 48).

Тест производительности выполняется для всех коллекций и складывает все значения свойств N для каждого элемента:

Видимо, что-то не так! Производительность нашей коллекции NaiveImmutableList<Т> такая же, как и у List. Что же произошло?

Возвращаемые ссылочные значения с модификатором readonly: как это работает

Как можно заметить, индексатор NaiveImmutableList<Т> возвращает ссылку, доступную только для чтения, с помощью модификатора ref readonly. Это полностью оправданно, так как мы хотим ограничить возможности клиентов в плане изменения основного состояния неизменяемой коллекции. Однако используемые нами в тесте производительности структуры доступны не только для чтения.

Данный тест поможет нам понять базовое поведение:

Тест прошел неудачно! Но почему? Потому что структура «ссылок, доступных только для чтения» похожа на структуру модификаторов in и полей readonly в отношении структур: компилятор генерирует защитную копию каждый раз, когда используется элемент структуры. Это значит, что ml[0]. по-прежнему создает копию первого элемента, но это делает не индексатор: копия создается в точке вызова.

Такое поведение на самом деле имеет смысл. Компилятор C# поддерживает передачу аргументов по значению, по ссылке и по «ссылке только для чтения», используя модификатор in (подробную информацию см. в публикации The in-modifier and the readonly structs in C# («Модификатор in и структуры только для чтения в C#»)). Теперь компилятор поддерживает три разных способа возврата значения из метода: по значению, по ссылке и по ссылке только для чтения.

«Ссылки только для чтения» настолько похожи на обычные, что компилятор использует один и тот же InAttribute для различения их возвращаемых значений:

В этом случае метод ByReadonlyRef эффективно компилируется в:

Сходство между модификатором in и ссылкой только для чтения означает, что эти функции не очень подходят для обычных структур и могут вызвать проблемы с производительностью. Рассмотрим пример:

Помимо необычного синтаксиса при объявлении переменной для bigStruct, код выглядит нормально. Цель ясна: BigStruct возвращается по ссылке из соображений производительности. К сожалению, поскольку структура BigStruct доступна для записи, каждый раз при доступе к элементу создается защитная копия.

Использование возвращаемых ссылочных значений в индексаторах. Попытка № 2

Давайте опробуем тот же набор тестов в отношении структур только для чтения разных размеров:

Теперь в результатах гораздо больше смысла. Время обработки по-прежнему увеличивается для больших структур, но это ожидаемо, потому что обработка более 100 тысяч структур большего размера занимает больше времени. Но теперь время работы для NaiveimmutableList<Т> очень близко ко времени T[] и значительно лучше, чем в случае с List.

Заключение

  • С возвращаемыми ссылочными значениями стоит обходиться осторожно, потому что они могут разорвать инкапсуляцию.
  • Возвращаемые ссылочные значения с модификатором readonly эффективны только для структур, доступных только для чтения. В случае с обычными структурами могут проявиться проблемы с производительностью.
  • При работе со структурами, доступными для записи, возвращаемые ссылочные значения с модификатором readonly создают защитную копию при каждом использовании переменной, что может вызвать проблемы с производительностью.

P. S. Ссылки только для чтения появятся в BCL. Методы readonly ref для доступа к элементам неизменных коллекций были представлены в следующем запросе на включение внесенных изменений в corefx repo (Implementing ItemRef API Proposal («Предложение на включение ItemRef API»)). Поэтому очень важно, чтобы все понимали особенности использования этих функций и то, как и когда их следует применять.

Похожие статьи