Чи нормально повернути значення аргументу за замовчуванням за допомогою посилання const?


26

Чи нормально повернути значення аргументу за замовчуванням за посиланням const, як у наведених нижче прикладах:

https://coliru.stacked-crooked.com/a/ff76e060a007723b

#include <string>

const std::string& foo(const std::string& s = std::string(""))
{
    return s;
}

int main()
{
    const std::string& s1 = foo();
    std::string s2 = foo();

    const std::string& s3 = foo("s");
    std::string s4 = foo("s");
}

3
Простий тест: замініть std::stringвласний клас, щоб ви могли відслідковувати будівництво та руйнування.
користувач4581301

1
@ user4581301 Якщо послідовність правильна, це не доведе, що конструкція все в порядку.
Пітер -

6
@ user4581301 "Здається, коли я спробував це" - це абсолютно найгірше, що стосується невизначеної поведінки
HerrJoebob

Слід зазначити, що питання є примхливим оманливим у своїй формулюванні. Ви не повертаєте значення аргументу за замовчуванням шляхом посилання const, але повертаєте посилання const на посилання const (... до аргументу за замовчуванням).
Деймон

2
@HerrJoebob Погоджуйтеся з твердженням на 100%, але не з контекстом, для якого ви його використовуєте. Як я прочитав це питання, це вирішує питання "Коли закінчиться термін життя об'єкта?" з'ясувати, коли викликається деструктор - це хороший спосіб зробити це. Для автоматичної змінної деструктор слід викликати вчасно або у вас виникли великі проблеми.
user4581301

Відповіді:


18

У вашому коді і те, s1і s3звисаючі посилання. s2і s4все в порядку.

У першому виклику тимчасовий порожній std::stringоб'єкт, створений з аргументу за замовчуванням, буде створений у контексті виразу, що містить виклик. Тому він помре в кінці визначення поняття s1, яке залишає s1звисаючим.

У другому дзвінку тимчасовий std::stringоб’єкт використовується для ініціалізації s2, після чого він гине.

У третьому дзвінку рядковий літерал "s"використовується для створення тимчасового std::stringоб'єкта, який також відмирає в кінці визначення s3, залишаючи s3звисаючий.

У четвертому виклику тимчасовий std::stringоб'єкт зі значенням "s"використовується для ініціалізації, s4а потім він гине.

Див. C ++ 17 [class.temporary] /6.1

Тимчасовий об'єкт, прив'язаний до опорного параметра у виклику функції (8.2.2), зберігається до завершення повного виразу, що містить виклик.


1
Цікавою частиною відповіді є твердження, що аргумент за замовчуванням буде створений у контексті абонента. Це, мабуть, підкріплене стандартною цитатою Гійом.
Пітер -

2
@ Peter-ReinstateMonica Див. [Expr.call] / 4, "... Ініціалізація та знищення кожного параметра відбувається в рамках функції виклику. ..."
Брайан

8

Це не безпечно :

Загалом, термін служби тимчасового не може бути продовжений шляхом "передачі його": друга посилання, ініціалізована з посилання, до якого тимчасовий прив'язується, не впливає на її тривалість життя.


Тож ви вважаєте, що std::string s2 = foo();це дійсно (зрештою, жодне посилання не передається прямо)?
Пітер -

1
@ Peter-ReinstateMonica, що один безпечний, оскільки буде створений новий об'єкт. Моя відповідь стосується лише розширення життя. Два інших відповіді вже охопили все. Я б не повторив у своєму.
Забуття

5

Це залежить від того, що ви робите зі струною згодом.

Якщо у вашому питанні правильний мій код? тоді так.

Від [dcl.fct.default] / 2

[ Приклад : Декларація

void point(int = 3, int = 4);

оголошує функцію, яку можна викликати з нулем, одним або двома аргументами типу int. Його можна викликати будь-яким із цих способів:

point(1,2);  point(1);  point();

Останні два виклики еквівалентні point(1,4)і point(3,4), відповідно. - кінцевий приклад ]

Отже ваш код фактично еквівалентний:

const std::string& s1 = foo(std::string(""));
std::string s2 = foo(std::string(""));

Весь ваш код правильний, але у жодному з цих випадків відсутнє подовження терміну експлуатації, оскільки тип повернення є посиланням.

Оскільки ви викликаєте функцію тимчасово, час повернення рядка не продовжить оператор.

const std::string& s1 = foo(std::string("")); // okay

s1; // not okay, s1 is dead. s1 is the temporary.

Ваш приклад з " s2Гаразд" добре, оскільки ви копіюєте (або переміщуєте) з тимчасового до кінця видання s3має таку ж проблему, як і s1.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.