Код ниже иллюстрирует мою озабоченность:
#include <iostream>struct O
{
~O()
{
std::cout << "~O()\n";
}
};
struct wrapper
{
O const& val;
~wrapper()
{
std::cout << "~wrapper()\n";
}
};
struct wrapperEx // with explicit ctor
{
O const& val;
explicit wrapperEx(O const& val)
: val(val)
{}
~wrapperEx()
{
std::cout << "~wrapperEx()\n";
}
};
template<class T>
T&& f(T&& t)
{
return std::forward<T>(t);
}int main()
{
std::cout << "case 1-----------\n";
{
auto&& a = wrapper{O()};
std::cout << "end-scope\n";
}
std::cout << "case 2-----------\n";
{
auto a = wrapper{O()};
std::cout << "end-scope\n";
}
std::cout << "case 3-----------\n";
{
auto&& a = wrapper{f(O())};
std::cout << "end-scope\n";
}
std::cout << "case Ex-----------\n";
{
auto&& a = wrapperEx{O()};
std::cout << "end-scope\n";
}
return 0;
}
Смотрите это в прямом эфире Вот.
Говорят, что auto&&
продлит время жизни временного объекта, но я не могу найти стандартные слова для этого правила, по крайней мере, в N3690.
Наиболее актуальным может быть раздел 12.2.5 о временном объекте, но не совсем то, что я ищу.
Итак, будет авто&& правило продления жизни все временные объекты, участвующие в выражении, или только конечный результат?
Более конкретно, это a.val
гарантированно будет действительным (не висят), прежде чем мы достигнем конца области в случае 1?
Редактировать:
Я обновил пример, чтобы показать больше случаев (3 & Ex).
Вы увидите, что только в случае 1 время жизни O увеличивается.
Точно так же, как ссылка на const
делает:
const auto& a = wrapper{O()};
или же
const wrapper& a = wrapper{O()};
или также
wrapper&& a = wrapper{O()};
Более конкретно, это
a.val
гарантированно будет действительным (не висят), прежде чем мы достигнем конца области в случае 1?
Да, это.
Нет ничего особенно важного auto
Вот. Это просто местодержатель для правильного типа (wrapper
) который выводится компилятором. Основным моментом является тот факт, что временный привязан к ссылке.
Для более подробной информации смотрите Кандидат на звание самого важного которые я цитирую:
Обычно временный объект длится только до конца полного выражения, в котором он появляется. Однако в C ++ специально указывается, что привязка временного объекта к ссылке на const в стеке удлиняет время жизни временного объекта до времени жизни самой ссылки.
Эта статья о C ++ 03, но аргумент все еще действителен: временный объект может быть связан со ссылкой на const
(но не на ссылку на неconst
). В C ++ 11 временная банка также быть привязанным к rvalue ссылке. В обоих случаях время жизни временного объекта увеличивается до времени жизни ссылки.
Соответствующие части Стандарта C ++ 11 в точности соответствуют тем, которые упоминаются в OP, то есть 12.2 p4 и p5:
4 — Существуют два контекста, в которых временные
другая точка, чем конец полного выражения. Первый контекст
является […]5 — Второй контекст, когда ссылка связана с временным. […]
(Есть некоторые исключения в пунктах маркера после этих строк.)
Обновить: (После комментария техасбруса.)
Причина, почему O
в случае 2 имеет короткий срок службы, что у нас есть auto a = wrapper{O()};
(видите, нет &
здесь), а затем временный не привязан к ссылке. Временный, на самом деле, копируется в a
используя сгенерированный компилятором конструктор копирования. Следовательно, время жизни временного элемента не увеличивается, и оно умирает в конце полного выражения, в котором оно появляется.
В этом конкретном примере есть опасность, потому что wrapper::val
это ссылка. Компилятор сгенерировал copy-конструктор wrapper
будет связывать a.val
к тому же объекту, что временный val
участник обязан. Этот объект также является временным, но имеет тип O
, Затем, когда этот последний временный умирает, мы видим ~O()
на экране и a.val
болтается!
Сравните случай 2 с этим:
std::cout << "case 3-----------\n";
{
O o;
auto a = wrapper{o};
std::cout << "end-scope\n";
}
Вывод (при компиляции с gcc с использованием опции -fno-elide-constructors
)
case 3-----------
~wrapper()
end-scope
~wrapper()
~O()
Сейчас временный wrapper
имеет свой val
член связан с o
, Заметить, что o
не временный. Как я сказал, a
является копией wrapper
временный и a.val
также связывает с
o
, Прежде чем сфера действия заканчивается временным wrapper
умирает и мы видим первый ~wrapper()
на экране.
Тогда сфера заканчивается, и мы получаем end-scope
, Сейчас, a
а также o
должны быть уничтожены в обратном порядке строительства, следовательно, мы видим ~wrapper()
когда a
умирает и наконец ~O()
когда это o
пора Это показывает, что a.val
не болтается
(Последнее замечание: я использовал -fno-elide-constructors
предотвратить оптимизацию, связанную с копированием, которая усложнит обсуждение здесь, но это другое история.)
Других решений пока нет …