Почему время жизни временного не увеличивается до времени жизни окружающего объекта? - PullRequest
28 голосов
/ 04 августа 2011

Я знаю, что временный не может быть привязан к неконстантной ссылке, но может быть привязан к константной ссылке. То есть

 A & x = A(); //error
 const A & y = A(); //ok

Я также знаю, что во втором случае (см. Выше) время жизни созданного временного из A() увеличивается до времени жизни константной ссылки (то есть y).

Но мой вопрос:

Может ли константная ссылка, которая связана с временной, быть дальше связана с еще одной константной ссылкой, продлевая время жизни временной до времени жизни второго объекта?

Я попробовал это, и это не сработало. Я не совсем понимаю это. Я написал этот код:

struct A
{
   A()  { std::cout << " A()" << std::endl; }
   ~A() { std::cout << "~A()" << std::endl; }
};

struct B
{
   const A & a;
   B(const A & a) : a(a) { std::cout << " B()" << std::endl; }
   ~B() { std::cout << "~B()" << std::endl; }
};

int main() 
{
        {
            A a;
            B b(a);
        }
        std::cout << "-----" << std::endl;
        {
            B b((A())); //extra braces are needed!
        }
}

Вывод ( ideone ):

 A()
 B()
~B()
~A()
-----
 A()
 B()
~A()
~B()

Разница в выходе? Почему временный объект A() разрушается перед объектом b во втором случае? В стандарте (C ++ 03) говорится об этом поведении?

Ответы [ 7 ]

17 голосов
/ 04 августа 2011

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

* 1003. Первый контекст - это когда выражение появляется как инициализатор для декларатора, определяющего объект. В этом контексте временное хранилище, которое содержит результат выражения, должно сохраняться до завершения инициализации объекта. [...]

§12.2 / 5 Второй контекст - это когда ссылка связана с временным. [...]

Ни один из этих двух вариантов не позволяет вам продлить срок службы временного объекта путем более позднего связывания ссылки с другой ссылкой const. Но проигнорируйте standarese и подумайте о том, что происходит:

Временные создаются в стеке. Ну, технически соглашение о вызовах может означать, что возвращаемое значение (временное), которое помещается в регистры, может даже не быть создано в стеке, но терпеть меня. Когда вы привязываете постоянную ссылку к временной переменной, компилятор семантически создает скрытую именованную переменную (поэтому конструктор копирования должен быть доступен, даже если он не вызывается) и привязывает ссылку к этой переменной. Будет ли копия на самом деле сделана или удалена - это деталь: у нас есть безымянная локальная переменная и ссылка на нее.

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

B* f() {
   B * bp = new B(A());
   return b;
}
void test() {
   B* p = f();
   delete p;
}

Теперь проблема в том, что временный объект (давайте назовем его _T) связан с f(), он ведет себя как локальная переменная там. Ссылка связана внутри *bp. Теперь время жизни этого объекта выходит за пределы функции, которая создала временный объект, но из-за того, что _T не был выделен динамически, это невозможно.

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

7 голосов
/ 04 августа 2011

Нет, расширенное время жизни не продлевается путем передачи ссылки.

Во втором случае временное значение привязывается к параметру a и уничтожается в концевремя жизни параметра - конец конструктора.

Стандарт прямо говорит:

Временная привязка к элементу ссылки в ctor-initializer конструктора (12.6.2) сохраняется доконструктор завершает работу.

4 голосов
/ 28 июня 2013

Ваш пример не выполняет вложенное продление жизни

В конструкторе

B(const A & a_) : a(a_) { std::cout << " B()" << std::endl; }

a_ здесь (переименован для экспозиции) не является временным. Является ли выражение временным, является синтаксическим свойством выражения, а id-выражение никогда не является временным. Таким образом, продление срока службы здесь не происходит.

Вот случай, когда может произойти продление жизни:

B() : a(A()) { std::cout << " B()" << std::endl; }

Однако, поскольку ссылка инициализируется в инициализаторе ctor, время жизни увеличивается только до конца функции. За [класс.время] р5 :

Временная привязка к элементу ссылки в конструкторе ctor-initializer (12.6.2) сохраняется до выхода из конструктора.

В вызове конструктору

B b((A())); //extra braces are needed!

Здесь мы привязываем ссылку к временному. [class.tevent] p5 говорит:

Временные связанный с опорным параметром в вызове функции (5.2.2) сохраняется до завершения полного выражения, содержащего вызов.

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

В других случаях выполняется вложенное продление жизни

Агрегированная переменная инициализации

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

struct X {
  const A &a;
};
X x = { A() };

В этом случае временное значение A напрямую связано со ссылкой, и поэтому временное значение продлевается на время жизни до x.a, что совпадает с временем жизни x. (Предупреждение: до недавнего времени очень немногие компиляторы понимали это правильно).

Совокупная временная инициализация

В C ++ 11 вы можете использовать агрегатную инициализацию, чтобы инициализировать временную, и, таким образом, получить рекурсивное продление времени жизни:

struct A {
   A()  { std::cout << " A()" << std::endl; }
   ~A() { std::cout << "~A()" << std::endl; }
};

struct B {
   const A &a;
   ~B() { std::cout << "~B()" << std::endl; }
};

int main() {
  const B &b = B { A() };
  std::cout << "-----" << std::endl;
}

С магистральным Clang или g ++ это дает следующий вывод:

 A()
-----
~B()
~A()

Обратите внимание, что временные A и временные B продлены на весь срок службы. Поскольку строительство временного A завершается первым, оно уничтожается последним.

В std::initializer_list<T> инициализация

C ++ 11 std::initializer_list<T> выполняет продление времени жизни, как будто путем привязки ссылки к базовому массиву. Поэтому мы можем выполнить вложенное время жизни, используя std::initializer_list. Однако ошибки компилятора распространены в этой области:

struct C {
  std::initializer_list<B> b;
  ~C() { std::cout << "~C()" << std::endl; }
};
int main() {
  const C &c = C{ { { A() }, { A() } } };
  std::cout << "-----" << std::endl;
}

Производит с хоботом Clang:

 A()
 A()
-----
~C()
~B()
~B()
~A()
~A()

и со стволом g ++:

 A()
 A()
~A()
~A()
-----
~C()
~B()
~B() 

Они оба не правы; правильный вывод:

 A()
 A()
-----
~C()
~B()
~A()
~B()
~A()
4 голосов
/ 04 августа 2011

§12.2 / 5 гласит: «Второй контекст [когда срок действия временного элемента увеличивается] - это когда ссылка связана с временным объектом». В буквальном смысле это ясно говорит о том, что срок службы должен бытьпродлен в вашем случае;ваш B::a, безусловно, связан с временным.(Ссылка привязывается к объекту, и я не вижу другого объекта, с которым он мог бы быть связан.) Однако это очень плохая формулировка;Я уверен, что имеется в виду «Второй контекст - это когда временный объект используется для инициализации ссылки», и расширенное время жизни соответствует таковому ссылки, инициализированной с помощью выражения rvalue, создающего временный объект,а не на какие-либо другие ссылки, которые впоследствии могут быть связаны с объектом.В его нынешнем виде формулировка требует чего-то, что просто не реализуемо: рассмотрим:

void f(A const& a)
{
    static A const& localA = a;
}

, вызываемый с помощью:

f(A());

Куда должен компилятор поместить A() (учитывая, что онкак правило, не может видеть код f() и не знает о локальной статической при генерации вызова)?

На самом деле я думаю, что это стоит DR.

IМогу добавить, что есть текст, который убедительно свидетельствует о правильности моей интерпретации намерения.Представьте, что у вас есть второй конструктор для B:

B::B() : a(A()) {}

В этом случае B::a будет непосредственно инициализирован временным;время жизни этого временного должно быть продлено даже по моей интерпретации.Однако стандарт делает конкретное исключение для этого случая;такое временное действие сохраняется только до выхода из конструктора (что опять-таки оставит вас с висящей ссылкой).Это исключение дает очень четкое указание на то, что авторы стандарта не собирались ссылаться на элементы в классе, чтобы продлить срок службы любых временных файлов, к которым они привязаны;Опять же, мотивация - осуществимость.Представьте себе, что вместо

B b((A()));

вы написали бы:

B* b = new B(A());

Куда компилятору поместить временное значение A(), чтобы его время жизни соответствовало динамически распределенному * 1030?*

2 голосов
/ 04 августа 2011

При первом запуске объекты уничтожаются в том порядке, в котором они были помещены в стек -> это нажатие A, нажатие B, всплывающее окно B, всплывающее окно A.

Во втором запуске, время жизни Aзаканчивается строительством б.Следовательно, он создает A, создает B из A, время жизни A заканчивается, поэтому он уничтожается, а затем B уничтожается.Имеет смысл ...

1 голос
/ 04 августа 2011

Я не знаю о стандартах, но могу обсудить некоторые факты, которые я видел в нескольких предыдущих вопросах.

1-й выход по очевидным причинам состоит в том, что a и b находятся в одной области видимости.Также a уничтожается после b, потому что оно построено до b.

Я предполагаю, что вы должны быть более заинтересованы во втором выводе.Прежде чем я начну, мы должны отметить, что следующие виды создания объектов (автономные временные):

{
  A();
}

длится только до следующих ; и не для блока, окружающего it. Демо .Во втором случае, когда вы это делаете,

B b((A()));

, таким образом, A() уничтожается, как только заканчивается создание объекта B().Поскольку константную ссылку можно привязать к временной, это не приведет к ошибке компиляции.Тем не менее, это обязательно приведет к логической ошибке, если вы попытаетесь получить доступ к B::a, который теперь привязан к уже вышедшей из области видимости переменной.

0 голосов
/ 08 августа 2011

§12.2 / 5 говорит

Временные связанный с опорным параметром в вызове функции (5.2.2) сохраняется до завершения полного выражения, содержащего вызов.

Довольно порезано и высушено, правда.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...