Расширение временного срока службы, работает с агрегатным блоком, но не через `new`; Зачем?

Примечание. Этот вопрос изначально был задан как комментарий Ryan Haining на этот ответ.


struct A { std::string const& ref; };

// (1)

A a { "hello world" };              // temporary lifetime is extended to that of `a`
std::cout << a.ref << std::endl;    // safe

// (2)

A * ptr = new A { "hello world" };  // lifetime of temporary not extended?
std::cout << ptr->ref << std::endl; // UB: dangling reference


Вопрос

  • Почему время жизни временного расширения в (1), но не в (2)?

Ответ 1

LONG STORY, SHORT

Компилятор не может продлить время жизни временного объекта new A { "temporary " }, поскольку созданный A и временный, имеет разные сроки хранения.

Подтверждение того, что говорит Стандарт, можно найти в конце этого сообщения. В Стандарте явно говорится, что срок жизни не будет расширен, но он не вдавается в подробности, почему это так.

Этот пост попробует объяснить причину таким образом, который понятен для более широкой аудитории, а не только в среднем language-lawyer.


Введение

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

Время автоматического хранения

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

  • Объекты, объявленные в области блока, имеют автоматическую продолжительность хранения (если они не объявлены static или extern, но не register).

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


Динамическое время хранения

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

  • Объекты, созданные динамически через operator new, имеют, как указано, динамическую продолжительность хранения.

    Хранилище будет сохраняться до тех пор, пока не будет выполнен соответствующий вызов operator delete.




Агрегатная инициализация с автоматическим временем хранения

Как указано в предыдущем разделе, временная автоматизированная продолжительность хранения.

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

Примечание. Мы можем представить, что они живут в одной и той же "коробке", и в конце области действия мы отбрасываем эту коробку, что прекрасно; ни временная, ни совокупность не переживут время жизни коробки.


Наша реализация (A)

struct A { std::string const& ref; };

void func () {
  A x { {"hello world"} };
}

За кулисами (A)

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

void  __func () {
  std::string __unnamed_temporary { "hello world" };
  A x { __unnamed_temporary };
}

Примечание: и временное, и агрегатное время жизни привязаны к текущей области, awesome!



Агрегатная инициализация с динамической продолжительностью хранения

Наша реализация (B)

A* gunc () {
  A *    ptr = new A { { "hello world" } };
  return ptr;
}

int main () {
  A * p = gunc ();

  std::cout << p->ref << std::endl; // DANGER, WILL ROBINSON!

  delete p;
}

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


За сценой (B)

Семантическая эквивалентность gunc может выглядеть как реализация ниже:

A* gunc () {
  A __unnamed_temporary { "hello world " };

  A * ptr = new A { __unnamed_temporary }; // (1)

  return ptr;
}

Вы тоже это думаете, не так ли?

Мы больше не можем продлить время жизни нашего временного, чтобы соответствовать времени A, созданному с динамической продолжительностью хранения, в (1).

Проблема заключается в том, что автоматическое хранилище для __unnamed_temporary исчезнет, ​​как только мы вернемся из gunc, эффективно убив наш временный.

Динамически созданный A, тем не менее, останется живым, оставив нас с обвисшей ссылкой в ​​main.



Заключение

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



Что означает стандарт (n3797)?

12.2p5 Временные объекты [class.temporary]

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

     

...

  •   
  • Временная привязка к ссылке в new-initializer (5.3.4) сохраняется до завершения полного выражения, содержащего новый-инициализатор.

         

    [Примечание. Это может привести к обманутой ссылке, и в этом случае рекомендуется внедрить предупреждение. - конец примечания]