Проверка моего понимания возврата функции

#c #function

#c #функция

Вопрос:

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

Вот краткое изложение моего понимания, надеюсь, кто-нибудь сможет это исправить:

Помещения:

 T foo() {
   ...
   return expr;
}

main() {
   T var = foo();
}
  

Правильно ли я понимаю процесс возврата, приведенный ниже?

  1. Результат вычисления expr неявно преобразуется в объявленный тип возвращаемой функции T . Это преобразование происходит внутри foo();
  2. Преобразованное значение выше используется для инициализации временного объекта, скажем, «x». Дополнительный вопрос: происходит ли это второе преобразование в foo() или main()?
  3. Временный объект «x» используется для инициализации переменной var в main().

Приветствуется любой ввод!

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

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

2. Существует множество переменных, влияющих на ваши вопросы (например, копирование текста, перемещение семантики и т.д.).

3. вы не указали, но современные компиляторы / C 11 оптимизируют операции временного типа / перемещения / копирования, используя «оптимизацию возвращаемого значения», о которой вы можете найти много информации.

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

5. @rabbitholedigger Обратите внимание, string amp; или просто string имеет большое значение в отношении поведения. Ваш вопрос довольно неспецифичен и широк.

Ответ №1:

Давайте подойдем к этому систематически.

Если функция объявлена как T f(); , а T таковой не является void , и если функция возвращается нормально, то она должна возвращаться через оператор вида return e; , где e есть некоторое выражение.

Когда вы оцениваете выражение вызова функции f() , вы получаете значение. Предположим, U обозначает тип объекта. Если T = U amp; или T = U amp;amp; , то значение имеет тип U , выражение e должно иметь возможность привязки к ссылке, а возвращаемое значение является значением e . (Возвращаемое значение также является так называемым «glvalue» с точки зрения его категории значений). В этом случае больше ничего не происходит. Значение вызова функции — это то, что возвращается.

Однако, когда T = U значение f() является так называемым «prvalue» («чистым rvalue»), и это требует создания временного объекта типа U . Этот объект сконструирован как будто с помощью U obj = e (т. Е. неявно преобразован из e ). Тогда значением f() является этот временный объект. Он может либо использоваться для инициализации еще одного объекта (например U x = f(); ), либо он может быть привязан к ссылке (например U amp;amp; r = f(); ).

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

 U f()
{
    std::locK_guard<std::mutex> lock(state_mutex);
    return state.get_value();
}
  

Здесь мы предполагаем, что инициализация U obj = state.get_value(); имеет смысл, и далее мы предполагаем, что она state.get_value() должна вызываться только во время state_mutex блокировки. Приведенный выше код делает это правильно и лаконично.

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

1. Действительно отличный ответ. Рассмотрена вся проблема в вопросе таким образом, который также объясняет категорию значений (с которой я на самом деле тоже борюсь).

Ответ №2:

  1. Вы правы, неявное преобразование происходит внутри foo . Подумайте, когда у вас есть более одного return оператора, и каждый из них возвращает свой тип — все эти типы должны быть преобразованы, прежде чем значение может быть возвращено.
  2. Второго преобразования нет. Первое преобразование происходит как часть построения или назначения временного.
  3. Правильно, за исключением того, что описано ниже.

Существует оптимизация, которую многие компиляторы будут выполнять, называемая copy elision, которая полностью пропускает временное значение.

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

1. Этот ответ охватил все проблемы. Проголосовал!

Ответ №3:

Ваше понимание по существу правильное.
Могут быть некоторые оптимизации, так что не так много шагов
копирования. Это может быть либо в форме return value optimization , либо
в c 11 move semantics

Что касается вашего дополнительного вопроса;
Вы не должны делать ни предположения, скажем, в пользовательских операторах преобразования,
деструкторах конструкторов и т.д.