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

Стандартная черновик С++ N4296 говорит

[class.temporary/5] Второй контекст - это когда ссылка привязана к временному. Временное, к которому привязана ссылка, или временное, являющееся полным объектом подобъекта, к которому привязана ссылка, сохраняется для времени жизни ссылки, кроме...

Итак, я хочу знать, что произойдет, если две или несколько ссылок привязаны к временному. Является ли он конкретным стандартным? Следующий пример может быть примером:

#include <iostream> //std::cout
#include <string> //std::string
const std::string &f() {
 const std::string &s = "hello";
 static const std::string &ss = s;
 return ss;
}
int main() {
 const std::string &rcs = f();
 std::cout << rcs; //empty output
 //the lifetime of the temporary is the same as that of s
 return 0;
}
</string></iostream>

Если мы изменим ограничивающий порядок, случай отличается.

#include <iostream> //std::cout
#include <string> //std::string
const std::string &f() {
 static const std::string &ss = "hello";
 const std::string &s = ss;
 return ss;
}
int main() {
 const std::string &rcs = f();
 std::cout << rcs; //output "hello"
 //the lifetime of the temporary is the same as that of ss
 return 0;
}
</string></iostream>

Компиляция выполняется на Ideone.com.

Я думаю, [class.temporary/5] выполняется только тогда, когда ссылка first привязана к временному, но я не могу найти доказательства в стандарте.

3 ответа

Это дефект в этом разделе, который я сообщил как http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1299.

Предлагаемая резолюция заключается в добавлении термина "временные выражения". Продление срока службы происходит только для объектов, на которые ссылаются временные выражения.

Вот мой оригинальный отчет, который я отправил по электронной почте. Я думаю, это ясно показывает, что это о

В модели Стандарта, как представляется, существует различие в отношении временные объекты и временные выражения.

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

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

Несколько абзацев относятся к "временным", но не явно указать, ссылаются ли они на временные объекты, на которые ссылаются произвольные выражения или относятся ли они только к временным выражения. В параграфах, касающихся RVO (пункт 12.8p31), используются "временные" в смысле временных объектов (говорят такие вещи как "объект временного класса, который не был привязан к ссылке" ). Пункты об увеличении продолжительности жизни (подпункт 12.2) относятся к оба вида временных. Например, в следующем, "* this" является не считающийся временным, хотя он относится к временному

struct A { A() { } A &f() { return *this; } void g() { } };
// call of g() is valid: lifetime did not end prematurely
// at the return statement
int main () { A().f().g(); }

В качестве другого примера основная проблема 462 касается временных выражений (делая выражение оператора запятой временным, если левый операнд был одним). Это, похоже, очень похоже на понятие "lvalue bitfields". Lvalues, которые отслеживают, что они ссылаются на битполы на времени перевода, чтобы считывания из них могли действовать соответствующим образом и что некоторые сценарии привязки ссылок могут испускать диагностику.


Что вам нужно знать, так это то, что ссылочный тип возврата не считается временным для целей этого раздела и никогда не приведет к продлению срока службы.

(примечание по педантике: стандарт требует, чтобы ссылка была привязана к значению x, а не только к временному. Вторая ссылка связана через lvalue, а не с xvalue.)

Ваш первый пример возвращает ссылку на свидание - строка cout - это undefined. Он может печатать Hello!, и это ничего не докажет.

Вот более простой пример:

template<class t="">
const T& ident(const T& in) { return in; }
int main(void)
{
 const X& ref1 = X(1); // lifetime extension occurs
 const X& ref2 = ident(X(2)); // no lifetime extension
 std::cout << "Here.\n";
}
</class>

Порядок строительства и разрушения:

X(1)
X(2)
~X() for X(2) object
"Here." is printed
~X() for X(1) object


Первая функция, представленная в вопросе,

const std::string &f() {
 const std::string &s = "hello";
 static const std::string &ss = s;
 return ss;
}

дает Undefined Поведение, если используется возвращаемая ссылка. Указанный объект перестает существовать, когда возвращается первый вызов функции. И последующие вызовы ss - это болтающаяся ссылка.

контекст стандартного абзаца продления жизни

С++ 11 §12.2/4

" Есть два контекста, в которых временные объекты уничтожаются в другой точке, чем конец полного выражения

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

Один из двух контекстов: с четырьмя отмеченными исключениями

C + 11 §12.2/5

" & hellip; когда ссылка привязана к [такой] временному

В приведенном выше коде временный std::string, созданный полным выражением "hello", привязан к ссылке s, а время жизни расширено до области s, которая является телом функции.

Последующее объявление и инициализация статической ссылки ss не включает полное выражение, создающее временное. Его выражение инициализации s не является временным: это ссылка на локальную. Следовательно, это вне контекста, охватываемого абзацем расширения жизни.

Но откуда мы знаем, что это значит? Ну, следя за тем, ссылается ли динамическая динамика на то, что изначально было временным, не является вычислимым для общего случая, а стандарт языка С++ не включает такие далекие понятия. Так просто, действительно.

ИМХО более интересный случай, прим. формальные правила,

#include <string>
#include <iostream>
using namespace std;
template< class Type >
auto temp_ref( Type&& o ) -> T& { return o; }
auto main() -> int
{
 auto const& s = temp_ref( string( "uh" ) + " oh!" );
 cout << s << endl;
}
</iostream></string>

Я утверждаю, что здесь нет расширения продолжительности жизни, и что выходной оператор, использующий ссылку s, использует обвисшую ссылку с результатом UB.

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

licensed under cc by-sa 3.0 with attribution.