В 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!
Почему то, что можно так легко и молчаливо злоупотреблять частью языка?
Обновить: суть может быть достаточно тонкой, чтобы оправдать некоторые пояснения: я не оспариваю использование правила, согласно которому «ссылки связаны с временными пользователями». Это все хорошо, и позволяет нам использовать неявные преобразования при привязке к ссылкам. Я спрашиваю о том, почему продолжительность жизни из временного влияет. Чтобы сыграть роль адвоката дьявола, я мог бы утверждать, что существующие правила «время жизни до конца полного выражения» уже охватывают случаи общего использования вызова функций с временными аргументами.
Простой ответ заключается в том, что вам нужно иметь возможность связывать временную ссылку с константной ссылкой, не имея этой функции, потребуется значительное количество дублирования кода, а функции должны принимать const&
для аргументов lvalue или value или by-value для аргументов rvalue.
Как только вам понадобится, в языке нужно определить некоторую семантику, которая будет гарантировать, что время жизни временного файла будет, по крайней мере, таким же, как у ссылки.
Как только вы соглашаетесь с тем, что ссылка может привязываться к r-значению в одном контексте, просто для согласованности вы можете захотеть расширить правило, чтобы разрешить такую же привязку в других контекстах, и семантика действительно одинакова. Временное время жизни увеличивается до тех пор, пока ссылка не исчезнет (будь то параметр функции или локальная переменная).
Альтернативой могут быть правила, которые разрешают связывание в некоторых контекстах (вызов функции), но не во всех (локальная ссылка), или правила, которые разрешают оба и всегда создают висячую ссылку в последнем случае.
Убрал цитату из ответа, оставил здесь, чтобы комментарии все же имели смысл:
Если вы посмотрите на формулировку в стандарте, есть некоторые подсказки относительно этого предполагаемого использования:
12.2 / 5 [середина абзаца] […] Временная привязка к ссылочному параметру в вызове функции (5.2.2) сохраняется до завершения полного выражения, содержащего вызов. […]
Как Бьярн Страуструп (оригинальный дизайнер) объяснил это в публикации в 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 );
}
Я обнаружил интересное приложение для продления жизни где-то здесь, на 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();