В чем смысл продления срока службы временных?

В C ++ время жизни временного значения можно продлить, связав его со ссылкой:

Foo make_foo();

{
Foo const & r1 = make_foo();
Foo && r2 = make_foo();

// ...
}             // both objects are destroyed here

Почему это разрешено? Какую проблему это решает?

Я не мог найти объяснение этому в Дизайн и Эволюция (например, 6.3.2: время жизни временных). Также я не мог найти какие-либо предыдущие вопросы по этому поводу (этот подошли ближе всего).

Эта функция несколько не интуитивна и имеет тонкие режимы отказа. Например:

Foo const & id(Foo const & x) { return x; }  // looks like a fine function...

Foo const & r3 = id(make_foo());             // ... but causes a terrible error!

Почему то, что можно так легко и молчаливо злоупотреблять частью языка?


Обновить: суть может быть достаточно тонкой, чтобы оправдать некоторые пояснения: я не оспариваю использование правила, согласно которому «ссылки связаны с временными пользователями». Это все хорошо, и позволяет нам использовать неявные преобразования при привязке к ссылкам. Я спрашиваю о том, почему продолжительность жизни из временного влияет. Чтобы сыграть роль адвоката дьявола, я мог бы утверждать, что существующие правила «время жизни до конца полного выражения» уже охватывают случаи общего использования вызова функций с временными аргументами.

19

Решение

Простой ответ заключается в том, что вам нужно иметь возможность связывать временную ссылку с константной ссылкой, не имея этой функции, потребуется значительное количество дублирования кода, а функции должны принимать const& для аргументов lvalue или value или by-value для аргументов rvalue.
Как только вам понадобится, в языке нужно определить некоторую семантику, которая будет гарантировать, что время жизни временного файла будет, по крайней мере, таким же, как у ссылки.

Как только вы соглашаетесь с тем, что ссылка может привязываться к r-значению в одном контексте, просто для согласованности вы можете захотеть расширить правило, чтобы разрешить такую ​​же привязку в других контекстах, и семантика действительно одинакова. Временное время жизни увеличивается до тех пор, пока ссылка не исчезнет (будь то параметр функции или локальная переменная).

Альтернативой могут быть правила, которые разрешают связывание в некоторых контекстах (вызов функции), но не во всех (локальная ссылка), или правила, которые разрешают оба и всегда создают висячую ссылку в последнем случае.


Убрал цитату из ответа, оставил здесь, чтобы комментарии все же имели смысл:

Если вы посмотрите на формулировку в стандарте, есть некоторые подсказки относительно этого предполагаемого использования:

12.2 / 5 [середина абзаца] […] Временная привязка к ссылочному параметру в вызове функции (5.2.2) сохраняется до завершения полного выражения, содержащего вызов. […]

14

Другие решения

Как Бьярн Страуструп (оригинальный дизайнер) объяснил это в публикации в clc ++ в 2005 году это было сделано по единым правилам.

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

for (int i = 0; i<xmax; ++i)
for (int j = 0; j< ymax; ++j) {
double& r = a[i][j];
for (int k = 0; k < zmax; ++k) {
// do something with a[i][j] and a[i][j][k]
}
}

Это может улучшить читаемость, а также производительность во время выполнения.

И это оказалось полезным для хранения объекта класса, производного от ссылочного типа, например, как в оригинальная реализация Scopeguard.


В публикация clc ++ в 2008 году, Джеймс Канзе предоставил еще несколько деталей:

Стандарт говорит точно, когда должен быть вызван деструктор. До
стандарт, однако, ARM (и более ранние языковые спецификации)
были значительно более свободными: деструктор мог быть вызван в любое время после
временный «использовался» и перед следующей закрывающей скобкой.

(«ARM» — это Аннотированное справочное руководство (IIRC) Бьярна Страуструпа и Маргарет Эллис, которое служило де-факто стандарт в последнее десятилетие до первого стандарта ISO. К сожалению, моя копия похоронена в коробке, под множеством других коробок, в пристройке. Так что я не могу проверить, но я считаю, что это правильно.)

Таким образом, как и во многих других, детали продления жизни были отточены и усовершенствованы в процессе стандартизации.

С тех пор как Джеймс поднял этот вопрос в комментариях к этому ответу: это совершенство не может быть достигнуто во времени, чтобы повлиять на обоснование Бьярне для продления жизни.


Пример Scopeguard-подобного кода, где временной привязкой к ссылке является полный объект производного типа, с деструктором производного типа, выполняемым в конце:

struct Base {};

template< class T >
struct Derived: Base {};

template< class T >
auto foo( T ) -> Derived<T> { return Derived<T>(); }

int main()
{
Base const& guard = foo( 42 );
}
9

Я обнаружил интересное приложение для продления жизни где-то здесь, на SO. (Я забыл где, я добавлю ссылку, когда я найду его.)

Увеличение продолжительности жизни позволяет нам использовать значения неподвижных типов.

Например:

struct Foo
{
Foo(int, bool, char);
Foo(Foo &&) = delete;
};

Тип Foo не может быть скопирован или перемещен. Тем не менее, мы можем иметь функцию, которая возвращает значение типа Foo:

Foo make_foo()
{
return {10, false, 'x'};
}

Тем не менее мы не можем построить локальную переменную, инициализированную с возвращаемым значением make_fooв общем, вызов функции создаст временный объект, который будет немедленно уничтожен. Lifetime extension позволяет нам использовать временный объект во всей области видимости:

auto && foo = make_foo();
2
По вопросам рекламы [email protected]