Урок №98. Передача по ссылке

  Юрий  | 

    | 

  Обновл. 29 мая 2019  | 

 16916

 ǀ   3 

Хотя передача по значению является хорошим вариантом во многих случаях, но она имеет несколько ограничений. Во-первых, при передаче по значению большой структуры или класса в функцию, создаётся копия аргумента и уже эта копия передаётся в параметр функции. Во многих случаях это бесполезная трата ресурсов, которая снижает производительность. Во-вторых, при передаче аргументов по значению, единственный способ вернуть значение обратно в вызывающий объект — это использовать возвращаемое значение функции. Но иногда случаются ситуации, когда нужно, чтобы функция изменила значение переданного аргумента. Передача по ссылке решает все эти проблемы.

Передача по ссылке

При передаче переменной по ссылке нужно просто объявить параметры функции как ссылки, а не как обычные переменные:

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

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

value = 6
value = 7

Как вы можете видеть, функция изменила значение аргумента из 6 на 7!

Вот ещё один пример:

Результат выполнения программы выше:

a = 7
a = 8

Обратите внимание, значение аргумента a было изменено функцией.

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


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

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

Давайте рассмотрим, как это работает, более подробно. Во-первых, в функции main() мы создаём локальные переменные sin и cos. Они передаются в функцию getSinCos() по ссылке (а не по значению). Это означает, что функция getSinCos() имеет прямой доступ к исходным значениям переменных sin и cos, а не к их копиям. getSinCos(), соответственно, присваивает новые значения переменным sin и cos (через ссылки sinOut и cosOut), перезаписывая их старые значения. Затем main() выводит эти обновлённые значения.

Если бы sin и cos были переданы по значению, а не по ссылке, то функция getSinCos() изменила бы копии sin и cos, а не исходные значения и эти изменения уничтожились бы в конце функции — переменные вышли бы из локальной области видимости. Но, поскольку sin и cos передавались по ссылке, любые изменения, внесённые в sin или cos (через ссылки), сохраняются и за пределами функции getSinCos(). Таким образом, мы можем использовать этот механизм для возврата сразу нескольких значений обратно в caller.

Хотя этот способ хорош, но он также имеет свои нюансы. Во-первых, синтаксис немного непривычен, так как параметры ввода и вывода указываются вместе с вызовом функции. Во-вторых, в caller-е не очевидно, что sin и cos являются параметрами вывода, и они будут изменены функцией. Это, вероятно, самая опасная часть этого способа (так как может привести к ошибкам). Некоторые программисты считают, что это достаточно большая проблема, и не советуют передавать аргументы по ссылке, а вместо неё советуют использовать передачу по адресу, не смешивая при этом параметры ввода и вывода.

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

Неконстантные ссылки могут ссылаться только на неконстантные l-values (например, на неконстантные переменные), поэтому параметр-ссылка не может принять аргумент, который является константным l-value или r-value (например, литералом или результатом выражения).

Передача по константной ссылке

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

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

Вы уже знаете, что константная ссылка — это ссылка на переменную, значение которой изменить через эту же ссылку не получиться никак. Следовательно, если мы используем константную ссылку в качестве параметра, то получаем 100% гарантию того, что функция не изменит аргумент!

В компиляции следующего фрагмента кода мы получим ошибку компилятора:

Использование const полезно по нескольким причинам:

   Мы получаем помощь компилятора в обеспечении того, что значения, которые не должны быть изменены — не изменятся (компилятор выдаст ошибку, если мы попытаемся сделать подобное, как в примере выше).

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

   Мы не можем передать константный аргумент в неконстантную ссылку-параметр. Использование константного параметра гарантирует то, что мы сможем передавать как неконстантные, так и константные аргументы в функцию.

   Константные ссылки могут принимать любые типы аргументов, включая l-values, константные l-values ​​и r-values.

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

Плюсы и минусы передачи по ссылке


Плюсы передачи по ссылке:

   Ссылки позволяют функции изменять значение аргумента, что иногда полезно. В противном случае, для гарантии того, что функция не изменит значение аргумента, нужно использовать константные ссылки.

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

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

Минусы передачи по ссылке:

   Трудно определить, является ли параметр, переданный по неконстантной ссылкой, параметром ввода, вывода или того и другого одновременно. Разумное использование const и суффикса Out для внешних переменных решает эту проблему.

   Из вызова функции невозможно определить, будет ли аргумент изменён функцией или нет. Аргумент, переданный по значению или по ссылке, выглядит одинаково. Мы можем определить, передаётся ли аргумент по значению или по ссылке, только просмотрев объявление функции. Это может привести к ситуациям, когда программист не сразу поймёт, что функция изменяет значение аргумента.

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

   при передаче структур или классов (используйте const, если нужно только для чтения);

   когда нужно, чтобы функция изменяла значение аргумента.

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

   при передаче фундаментальных типов данных (используйте передачу по значению);

   при передаче обычных массивов (используйте передачу по адресу).

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (88 оценок, среднее: 4,91 из 5)
Загрузка...

Комментариев: 3

  1. Аватар smigles:

    Почему автор не советует передавать фундаментальные типы по ссылке? Ведь тогда тоже не произойдёт ненужного копирования значения. А чтобы нельзя было изменить значение аргумента, достаточно к параметру добавить const.

    1. Аватар kmish:

      Вероятно потому, что памяти для создания ссылки требуется больше, чем для фундаментальных типов, ну или по крайней мере не меньше.

      1. Аватар Александр:

        вряд-ли…
        все понятия, с которыми нам удобно работать при написании программ: ссылки, переменные, константы и тому подобное, существуют только до того, как отработает компилятор.

        Когда мы что-то объявляем константой, то запрет на изменения этого чего-то нам ставит компилятор… соответственно как-то дополнительно отмечать константы или ссылки в итоговом коде смысла никакого нет

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

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *