Подтвердить что ты не робот

Почему срок жизни не продлевается до срока пребывания объекта-объекта?

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

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

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

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

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

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

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 во втором случае? Говорит ли стандарт (С++ 03) об этом?

4b9b3361

Ответ 1

В стандарте рассматриваются два обстоятельства, при которых продлевается время жизни временного:

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

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

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

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

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

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

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

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

Ответ 2

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

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

В стандарте явно указано:

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

Ответ 3

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

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

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

f(A());

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

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

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

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

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

B b((A()));

вы написали:

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

Где должен компилятор поставить временную A() так, чтобы она была на всю жизнь это будет динамически распределенный B?

Ответ 4

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

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

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

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

Здесь будет происходить случай расширения продолжительности жизни:

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

Однако, поскольку ссылка инициализируется в ctor-инициализаторе, время жизни продолжается только до конца функции. Per [class.temporary] p5:

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

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

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

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

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

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

Другие случаи выполняют вложенное расширение продолжительности жизни

Агрегатная инициализация переменных

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

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

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

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

В С++ 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()

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

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

С++ 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++ trunk:

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

Они оба ошибаются; правильный выход:

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

Ответ 5

В вашем первом прогоне объекты уничтожаются в том порядке, в котором они были нажаты в стеке → , который нажимает A, нажимает B, pop B, pop A.

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

Ответ 6

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

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

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

{
  A();
}

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

B b((A()));

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

Ответ 7

В §12.2/5 говорится:

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

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