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

Стандартный проект C++ 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;
}

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

#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;
}

Сборка производится на Ideone.com.

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


person xskxzr    schedule 21.11.2015    source источник
comment
Вы не можете привязать временный объект к более чем одной ссылке. Единственным временным (= prvalue) в вашем примере является объект std::string, созданный из "hello", и он привязан к первой ссылочной переменной. Вторая ссылка привязана к lvalue, полученному в результате оценки id-выражения, содержащего первую переменную.   -  person Kerrek SB    schedule 21.11.2015
comment
@KerrekSB Вы не можете привязать временную ссылку к более чем одной ссылке ... угадайте еще раз coliru.stacked-crooked .com/a/edfbe82db89ea4ab   -  person Lorah Attkins    schedule 21.11.2015
comment
@Kerrek SB [N4296/expr/5] говорит, что если выражение изначально имеет тип «ссылка на T» (8.3.2, 8.5.3), перед любым дальнейшим анализом тип настраивается на T. Выражение обозначает объект или функцию, обозначенную ссылкой, и выражение является значением l или значением x, в зависимости от выражения. Поэтому я думаю, что вторая ссылка действительно связана с временным.   -  person xskxzr    schedule 21.11.2015
comment
Вот цитата из cppreference (не нормативная): В общем, время жизни временного объекта не может быть дополнительно продлено путем его передачи: вторая ссылка, инициализированная ссылкой, к которой был привязан временный объект, не влияет на его время жизни. .   -  person 0x499602D2    schedule 21.11.2015
comment
@LorahAttkins: А кто в вашем примере временный? :-)   -  person Kerrek SB    schedule 21.11.2015
comment
@Shenke: Но временное значение должно быть prvalue. Если это не prvalue, это уже не временно.   -  person Kerrek SB    schedule 21.11.2015
comment
@KerrekSB Я был неправ :/   -  person Lorah Attkins    schedule 21.11.2015
comment
@KerrekSB Понял. Спасибо!   -  person xskxzr    schedule 21.11.2015
comment
@KerrekSB: временный объект — это объект. Объекты не имеют категорий стоимости; выражения делают.   -  person Lightness Races in Orbit    schedule 21.11.2015


Ответы (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». Lvalues, которые отслеживают, что они ссылаются на битовые поля во время трансляции, так что чтение из них может действовать соответствующим образом, и чтобы определенные сценарии связывания ссылок могли выдавать диагностику.

person Johannes Schaub - litb    schedule 21.11.2015
comment
@Johannes: Есть ли разница между временным выражением и значением r? Я думаю, что с вариантами rvalue для С++ 11 обязательно должно подойти? - person Cheers and hth. - Alf; 21.11.2015
comment
@Cheersandhth.-Alf: Это должно быть значение. Значения X определенно не подходят, поэтому с struct X { int a; } int && r = X{}.a; не продлевает время жизни. (Начиная с C++14, RHS является значением x.) - person Kerrek SB; 21.11.2015
comment
@KerrekSB: Простите меня за то, что я просто ловлю рыбу (мне неясно в этом вопросе), но есть формулировка о продлении времени жизни объекта, когда его часть привязана к ссылке. Что может быть примером этого? - person Cheers and hth. - Alf; 21.11.2015
comment
О, Base const& o = Derived() подойдет, извините. - person Cheers and hth. - Alf; 21.11.2015
comment
@Cheersandhth.-Alf: Точно :-) Имейте в виду, что стандарт все еще довольно неясен в этом отношении, например. при привязке элементов массива: int && a = T()[4]; с using T = int[10]; и т.д. - person Kerrek SB; 22.11.2015
comment
@Cheersandhth.-Alf 0 не является временным выражением (оно не относится к временному объекту). Но это цена. - person Johannes Schaub - litb; 22.11.2015
comment
@ᐅJohannesSchaub-litbᐊ: Но int const& nada = 0; совершенно нормально, не так ли? И 0 не временное, и вы говорите, что это не временное выражение. Итак, можем ли мы сделать вывод, что для стандарта правильно использовать термин prvalue? - person Cheers and hth. - Alf; 22.11.2015
comment
@Cheersandhth.-Alf Что касается значений prvalue класса, я думаю, что они всегда являются временными выражениями (по крайней мере, после того, как они исправили prvalue.member, чтобы оно никогда не было значением x, если член является нестатическим членом. Раньше это было невременное значение prvalue), то же самое для массивов. - person Johannes Schaub - litb; 22.11.2015
comment
@Cheersandhth.-Alf Я не уверен, но я был бы склонен сделать это более понятным и не использовать prvalue. Потому что я думаю, что хотел бы, чтобы (temporary expression).nonStaticMember было временным выражением, даже если это значение x. Потому что это подобъект временного объекта, и предполагается, что его время жизни будет продлено, если оно связано ссылкой. - person Johannes Schaub - litb; 22.11.2015
comment
@KerrekSB Я думаю, что то, что вы сказали выше о том, что X{}.a не продлевается срок службы, поскольку C ++ 14, неверно. У нас всегда было специальное правило для случая, когда выражение доступа к члену на временном сроке жизни расширяет полный (временный) объект объекта-члена. - person Johannes Schaub - litb; 22.11.2015
comment
@Cheersandhth.-Alf, чтобы быть максимально ясным: int const& nada = 0 работает не потому, что 0 является значением prvalue, а потому, что ссылка на самом деле не привязана к 0, а привязана к временному инициализированному из 0. - person Johannes Schaub - litb; 22.11.2015
comment
Да, я имел в виду (именно так) исправление должно разрешать этот случай. - person Cheers and hth. - Alf; 22.11.2015
comment
Должно быть достаточно изменить правило о продлении срока действия, чтобы оно требовало прямого связывания, так как 8.5.3 определяет прямое связывание, чтобы включить точно правильный набор случаев. - person Ben Voigt; 22.11.2015

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

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

Ваш первый пример возвращает висячую ссылку - строка cout имеет неопределенное поведение. Он может напечатать 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";
}

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

X(1)
X(2)
~X() for X(2) object
"Here." is printed
~X() for X(1) object
person Ben Voigt    schedule 21.11.2015
comment
Спасибо @ 0x499602D2: Хороший улов, это не демонстрировало то, что я хотел. Теперь исправлено. - person Ben Voigt; 21.11.2015
comment
Что касается требования xvalue, я не могу его найти. Можете ли вы предоставить ссылку? - person Cheers and hth. - Alf; 22.11.2015
comment
@Alf: Прямая привязка связана с xvalue и prvalue (которые являются xvalues) в последних нескольких абзацах 8.5.3. Временное продление срока службы происходит только для прямого связывания, хотя, как указывает Йоханнес, в правиле продления срока службы слово прямое опущено, оно только подразумевается. - person Ben Voigt; 22.11.2015
comment
@BenVoigt Я думаю, что это ложь, или я не понимаю, что ты говоришь. For int &&a = 0; есть продление жизни, но привязка временной, созданной во время инициализации ссылки, не имеет никакой привязки, ни прямой, ни косвенной, а привязка 0 к ссылке имеет косвенную привязку (поскольку только временная созданная будет граница). - person Johannes Schaub - litb; 22.11.2015
comment
@ ᐅJohannesSchaub-litbᐊ: Хммм, мое прочтение 8.5.3 говорит, что выражение действительно связывает напрямую, но при дальнейшем рассмотрении формулировка 8.5.3 в n4527 кажется сильно нарушенной из-за неполных правок. - person Ben Voigt; 22.11.2015
comment
@Ben формулировка В противном случае создается временный тип « cv1 T1 » и инициализируется копированием ([dcl.init]) из выражения инициализатора. Затем ссылка привязывается к временному. который является последним маркером-различением, и во всех случаях, кроме последнего (т. е. создание и инициализация временного выражения из выражения инициализатора), считается, что ссылка привязывается непосредственно к выражению инициализатора. Следовательно, существует косвенная привязка 0. Также обратите внимание, что привязка выполняется к выражению инициализатора (то есть 0). Это выглядит достаточно ясным для меня. - person Johannes Schaub - litb; 22.11.2015
comment
@ ᐅJohannesSchaub-litbᐊ: в n4527 последний случай (тот, который, как говорят, не является прямой привязкой) — это Если T1 связан со ссылкой на T2: cv1 должен иметь ту же cv-квалификацию, что и cv2, или большую cv-квалификацию, чем cv2 ; и если ссылка является ссылкой rvalue, выражение инициализатора не должно быть lvalue. - person Ben Voigt; 22.11.2015
comment
@BenVoigt, что пуля не является последним упомянутым случаем, а просто ограничивает случаи «Иначе:» перед ним с помощью определенных правил. Утверждение, что Foo не должен быть баром, является прямой или косвенной привязкой, не имеет смысла. - person Johannes Schaub - litb; 22.11.2015
comment
@Ben см. stackoverflow.com/ вопросы/33855956/ . - person Johannes Schaub - litb; 22.11.2015

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

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

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

Контекст параграфа о продлении срока действия стандарта:

C++11 §12.2/4

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

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

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

C+11 §12.2/5

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

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

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

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


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

#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;
}

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

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

person Cheers and hth. - Alf    schedule 21.11.2015
comment
В своем выражении инициализатора s не является временным: это ссылка на локальную, вы имеете в виду, что поиск имени находит локальную, а не концепцию ссылки С++, верно? - person Ben Voigt; 22.11.2015
comment
Я имею в виду, что выражение не производит временное. Сам по себе он не создает временный объект, и, поскольку он имеет тот же тип, что и инициализируемый объект, он не используется для создания временного объекта. - person Cheers and hth. - Alf; 22.11.2015