Подтвердить что ты не робот

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

В С++ время жизни временного значения может быть расширено путем привязки его к ссылке:

Foo make_foo();

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

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

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

Я не мог найти объяснения этому в Design and Evolution (например, 6.3.2: Lifetime of Temporaries). Я также не мог найти какие-либо предыдущие вопросы об этом (этот был ближе всего).

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

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

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

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


Обновление: точка может быть достаточно тонкой, чтобы оправдать некоторые пояснения: я не оспариваю использование правила, которое "привязывает ссылки к временным". Это хорошо и хорошо, и позволяет нам использовать неявные con & shy; ver & shy; sions при привязке к ссылкам. То, о чем я прошу, - это то, почему пострадало время жизни. Чтобы играть адвоката дьявола, я мог бы утверждать, что существующие правила "времени жизни до конца полного выражения" уже охватывают общие случаи использования функций с временными аргументами.

4b9b3361

Ответ 1

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

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

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


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

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

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

Ответ 2

Как Bjarne Stroustrup (оригинальный дизайнер) объяснил это в публикации clС++ в 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.


В публикация clС++ в 2008 году Джеймс Канзе предоставил более подробную информацию:

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

( "ARM" - это аннотированное справочное руководство (IIRC) Бьярне Страуструп и Маргарет Эллис, которые служили стандартом de facto за последнее десятилетие до первого стандарта 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 );
}

Ответ 3

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

Расширение Lifetime позволяет использовать prvalues ​​неподвижных типов.

Например:

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

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

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

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

auto && foo = make_foo();