Примітка: ця відповідь стосується лише c ++ 11 і далі. Не існує такого поняття, як "C / C ++", це різні мови.
Ні, не існує небезпеки повернення локального об’єкта за вартістю, і рекомендується це робити. Однак я думаю, що тут є важливий момент, який бракує у всіх відповідях тут. Багато інших говорили, що структура копіюється або розміщується безпосередньо за допомогою RVO. Однак це не зовсім правильно. Я спробую пояснити, що саме може статися при поверненні локального об'єкта.
Переміщення семантики
Починаючи з ++ ++, у нас є посилання rvalue, які є посиланнями на тимчасові об'єкти, які можна безпечно викрасти. Як приклад, std :: vector має конструктор переміщення, а також оператор присвоєння переміщення. Обидва вони мають постійну складність і просто копіюють покажчик на дані вектора, з якого переміщується. Я не буду детальніше розповідати про семантику переміщення тут.
Оскільки об'єкт, створений локально в межах функції, є тимчасовим і виходить за межі обсягу, коли функція повертається, повернутий об'єкт ніколи не є копіюється разом із c ++ 11 далі. Конструктор переміщення викликається на повертається об'єкт (чи ні, пояснюється пізніше). Це означає, що якщо ви повинні повернути об’єкт із дорогим конструктором копіювання, але недорогим конструктором переміщення, як великий вектор, лише право власності на дані передається від локального об’єкта до поверненого об’єкта - що дешево.
Зверніть увагу, що у вашому конкретному прикладі немає різниці між копіюванням та переміщенням об’єкта. За замовчуванням конструктори переміщення та копіювання вашої структури призводять до тих самих операцій; копіювання двох цілих чисел. Однак це принаймні так само швидко, ніж будь-яке інше рішення, оскільки вся структура поміщається в 64-розрядний регістр ЦП (виправте мене, якщо я помиляюся, я не знаю багато регістрів ЦП).
RVO та NRVO
RVO означає оптимізацію поверненої вартості і є однією з небагатьох оптимізацій, яку виконують компілятори, що може мати побічні ефекти. З c ++ 17 необхідний RVO. Коли повертається неназваний об'єкт, він створюється безпосередньо на місці, де абонент призначає повернене значення. Ні конструктор копіювання, ні конструктор переміщення не викликаються. Без RVO безіменний об'єкт спочатку будується локально, потім переміщується побудованим за повернутою адресою, потім локальний безіменний об'єкт руйнується.
Приклад, коли RVO потрібен (c ++ 17) або ймовірно (до c ++ 17):
auto function(int a, int b) -> MyStruct {
return MyStruct{a, b};
}
NRVO означає Оптимізацію іменованого поверненого значення, і це те саме, що RVO, за винятком того, що це робиться для іменованого об'єкта, локального для викликаної функції. Це все ще не гарантується стандартом (c ++ 20), але багато компіляторів все ще роблять це. Зауважте, що навіть із названими локальними об’єктами вони в гіршому випадку переміщуються при поверненні.
Висновок
Єдиний випадок, коли вам слід врахувати не повернення за значенням, це коли у вас є іменований, дуже великий (як у його розмірі стека) об’єкт. Це пов’язано з тим, що NRVO ще не гарантований (станом на c ++ 20), і навіть переміщення об’єкта було б повільним. Моя рекомендація та рекомендація в Основних рекомендаціях Cpp полягає у тому, щоб завжди віддавати перевагу поверненню об’єктів за значенням (якщо кілька повернутих значень, використовуйте struct (або кортеж)), де єдиним винятком є випадки, коли об’єкт дорогий для переміщення. У цьому випадку використовуйте неконстантний посилальний параметр.
НІКОЛИ не є гарною ідеєю повертати ресурс, який потрібно вручну звільнити від функції в c ++. Ніколи цього не роби. Принаймні використовуйте std :: unique_ptr, або створіть власну нелокальну або локальну структуру за допомогою деструктора, який звільняє свій ресурс ( RAII ) і повертає екземпляр цього. Тоді також було б непоганою ідеєю визначити конструктор переміщення та оператор присвоєння переміщення, якщо ресурс не має власної семантики переміщення (та видалити конструктор копіювання / призначення).