[БЕЗ_ЗВУКА] В предыдущих видео мы подробно обсудили, как же правильно передавать объекты в функцию. Настала пора обсудить, как же объекты из функции возвращать. Хотя постойте, мы же это обсуждали в первом курсе. Объекты из функции возвращаются через return. Ну то есть я здесь ничему новому вас не научу. Счастливо оставаться! Такси! Вы еще здесь? Ладно, давайте договоримся, что вы будете использовать return и только return, а я расскажу вам о преимуществах этого подхода. Итак, return — это всем известный, максимально удобный и понятный способ вернуть данные из функции. Потому что во-первых, прямо по сигнатуре функции видно, что она возвращает, внутри функциивидно выражение return что-то, функцию завершили, данные вернули. И наконец, специально для return придуманы оптимизации copy elision, named return value optimization, ну и, собственно, move-семантика, которую мы обсуждали в предыдущем курсе. Именно поэтому вам стоит максимально использовать return. Однако, конечно, здесь не без подводных камней, и есть ситуации, когда при возврате некоторых объектов из функции у вас копирование неизбежно. А именно, если у объекта много данных на стеке. Давайте продемонстрируем такой пример. Переключимся в IDE. Вот пусть у нас есть некоторая структура UserInfo с некоторыми данными о пользователе. И вы пишете большую рекомендательную систему, у которой большое будущее. И, ну допустим, про пользователя вы уже много всего знаете, и чем больше данных вы соберете о пользователе, тем лучше будут ваши рекомендации. Поэтому наверняка там будут не только имя и возраст, но и, например, лояльность этого пользователя в виде некоторого вещественного числа, скажем, интересуют ли этого пользователя машины. Или, например, коллеги data-scientist-ы принесли вам для каждого пользователя вектор из 20 вещественных чисел. Ну и вы сложили его в массив и назвали его ml_data. И пока все хорошо. Если вы из функции будете возвращать конкретную переменную, то все тоже будет в порядке, у вас сработает named return value optimization. Но если вдруг у нас функция будет устроена по-другому, и в return будет написано что-то вроде ReadUser().info, то есть будет возвращаться не локальная переменная, а какой-то другой, пусть даже временный объект, точнее, поле временного объекта, то в этом случае не сработает ни copy ellision, ни named return value optimization. А move-семантика нам не поможет, потому что у этой структуры со временем будет все больше и больше данных на стеке. Что же делать в этой ситуации? Мы могли бы сказать, что нужно отказаться от return, но я скажу вам нет. Потому что если вы имеете дело с такими объектами, объектами, у которых много данных на стеке, то у вас будет от них гораздо больше проблем, чем просто при возврате из функции. Например, если у вас в функции main есть вектор таких данных о пользователе, users, представьте, что вы хотите его отсортировать по какому-то признаку, не очень важно, по какому. sort(begin(users), end(users)); Выглядит хорошо, но на самом деле сортировка внутри будет переставлять элементы местами, а у этих элементов много данных на стеке, и поэтому это будет долго. Или еще один довольно безобидный пример. Что, если мы хотим из этого вектора достать всех пользователей, у которых лояльность больше какого-то порога. То есть у нас есть функция FindLoyalUsers, которая принимает набор данных о пользователях, некий порог лояльности 20. И тех пользователей, которых мы получили из этой функции, мы сохраняем в некий вектор vector<UserInfo> loyal_users. В этом случае у нас опять же будут копирования этих пользователей из исходного вектора в получившийся. Поэтому объекты, у которых много данных на стеке, опасны в любом случае. Вам все равно рано или поздно придется переписывать очень много кода, избавляясь от копирований этих объектов. Поэтому есть такой интересный совет по работе с такими объектами, а именно — оборачивать их в unique pointer-ы. Давайте мы подключим модуль memory. Структуру оставляем, как есть, но функция ReadUserInfo — давайте я верну ее в исходное состояние — будет возвращать unique_ptr от UserInfo. Ну и функция должна выглядеть примерно следующим образом. Мы сначала создадим info_ptr с помощью вызова функции make_unique от UserInfo, ну и в данном случае конструктор у нас пустой. И затем можно создать ссылку info, чтобы было удобнее работать, разлиновав этот указатель, дальше с помощью этой ссылки все заполнить и вернуть info_ptr. И везде дальше хранить не UserInfo непосредственно, чтобы не было лишних копирований, а unique_ptr. Можно даже ему присвоить некоторое название с помощью директивы using. using InfoPtr = unique_ptr от UserInfo. И здесь у нас будет InfoPtr. И в векторе мы будем хранить именно InfoPtr. Преимущество этого подхода в том, что мы отселяем объект, у которого много данных на стеке, мы отселяем эти данные в кучу. И благодаря этому у нас все становится хорошо. Объекты хранятся в одном конкретном месте и никуда оттуда не перемещаются, только если мы явно не захотим как-то их скопировать. Ну и теперь у нас и сортировка будет в порядке, потому что будем переставлять указатели на кучу, и вот такие сценарии вида, когда мы хотим оставить каких-то пользователей из диапазона, будут максимально простыми. Итак, подведем итоги этого видео. Как же возвращают объекты из функции? Объекты из функции возвращают с помощью return с одним единственным исключением, что если у вашего объекта много данных на стеке, то его стоит отселить в кучу, обернув в unique_ptr. В следующем видео мы обсудим, как же вернуть из функции несколько объектов разных типов.