Можно ли вернуть значение аргумента по умолчанию с помощью константной ссылки?

26

Можно ли вернуть значение аргумента по умолчанию с помощью константной ссылки, как в следующих примерах:

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собственным классом, чтобы вы могли отслеживать строительство и разрушение.
user4581301
1
@ user4581301 Если последовательность правильная, это не доказывает, что конструкция в порядке.
Питер - Восстановить Монику
6
@ user4581301 «Похоже, сработало, когда я попробовал» - это самое страшное в неопределенном поведении
HerrJoebob
Следует отметить, что этот вопрос является обманчивым в своей формулировке. Вы не возвращаете значение аргумента по умолчанию с помощью константной ссылки, но вы возвращаете константную ссылку на константную ссылку (... на аргумент по умолчанию).
Дэймон
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.teven] /6.1

Временный объект, связанный с опорным параметром в вызове функции (8.2.2), сохраняется до завершения полного выражения, содержащего вызов.

Брайан
источник
1
Интересной частью ответа является утверждение, что аргумент по умолчанию будет создан в контексте вызывающего. Это подтверждается стандартной цитатой Гийома.
Питер - Восстановить Монику
2
@ Peter-ReinstateMonica См. [Expr.call] / 4, «... Инициализация и уничтожение каждого параметра происходит в контексте вызывающей функции. ...»
Брайан,
8

Это не безопасно :

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

забвение
источник
Так вы думаете, std::string s2 = foo();является действительным (в конце концов, никакие ссылки не передаются явно)?
Питер - Восстановить Монику
1
@ Peter-ReinstateMonica, что каждый безопасен, поскольку новый объект будет построен. Мой ответ просто о продлении жизни. Два других ответа уже охватили все. Я бы не стал повторять по моему.
Oblivion
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хорошо, так как вы копируете (или перемещаете) из временного до конца satement. s3имеет ту же проблему, чем s1.

Гийом Расико
источник