Создайте не временный объект в конструкторе в c ++ [duplicate]

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

Второе преимущество: он позволяет обмениваться интерфейсами без совместного использования кода между разными единицами (разные разработчики, команды, компании и т. Д.) [ ! d1]

28
задан Nawaz 4 August 2011 в 07:30
поделиться

7 ответов

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

§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 () , она ведет себя как a там есть локальная переменная. Ссылка связана внутри * bp . Теперь время жизни объекта выходит за пределы функции, создавшей временную, но поскольку _T не было динамически распределено, это невозможно.

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

16
ответ дан David Rodríguez - dribeas 16 August 2018 в 00:28
поделиться
  • 1
    +1. Отличное объяснение. – Nawaz 4 August 2011 в 09:38
  • 2
    @Nawaz: Обычно я создаю ментальные диаграммы с объектами и что происходит, подобно маленьким изображениям, которые вы можете найти здесь здесь для NRVO. Способность рисовать помогает понять, а также помогает мне запомнить. – David Rodríguez - dribeas 4 August 2011 в 12:59

§12.2 / 5 говорит

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

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

0
ответ дан Ben Voigt 16 August 2018 в 00:28
поделиться

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

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

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

Временная привязка к ссылочному элементу в ctor-инициализаторе конструктора (12.6. 2) сохраняется, пока конструктор не выйдет.

7
ответ дан Bo Persson 16 August 2018 в 00:28
поделиться
  • 1
    В этой цитате не говорится о привязке далее к еще одной константной ссылке, которая является членом класса. Поэтому я немного скептически настроен. – Nawaz 4 August 2011 в 07:37
  • 2
    Стандарт явно перечисляет несколько мест, где время жизни расширено. Ваш случай не упоминается, предполагая, что его там не бывает. – Bo Persson 4 August 2011 в 07:42
  • 3
    Нет «расширенного срока службы». Передача временной ссылки const не продлевает ее время жизни, временное все еще уничтожается в конце полного выражения. – fredoverflow 4 August 2011 в 10:55
  • 4
    Это не применимое правило. В C ++ 0x правило во временных данных передается как аргументы функции. Я не знаю, имеет ли C ++ 03 такое правило. – Ben Voigt 8 August 2011 в 00:36

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

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

с:

  f (A ());   

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

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

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

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

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

  B b ((A ()));   

вы написали:

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

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

4
ответ дан Eric 16 August 2018 в 00:28
поделиться
  • 1
    +1. Удивительный анализ стандартного текста. – Nawaz 4 August 2011 в 09:44
  • 2
    Я не согласен, что B :: a привязан к временному. Выражение, которое оно связано, формируется (неявным) разыменованием параметра. Это l-значение (хотя const ), а не временное, в этом контексте. Текст C ++ 0x также очень ясен об этих случаях: «Временная привязка к эталонному параметру в вызове функции (5.2.2) сохраняется до завершения полного выражения, содержащего вызов. & Quot; и "Временная привязка к ссылке в новом инициализаторе (5.3.4) сохраняется до завершения полного выражения, содержащего новый инициализатор. & quot; – Ben Voigt 8 August 2011 в 00:29
  • 3
    @Ben Voigt Это вопрос терминологии. Ссылка не связана с выражением. Он связан с объектом. Ссылка инициализируется выражением; если это выражение является lvalue, оно привязано к объекту, обозначенному этим lvalue. Если lvalue является ссылкой, которая обозначает временную, то ссылка привязана к этому временному (объекту). – James Kanze 8 August 2011 в 08:58
  • 4
    @James: Все верно. Но независимо от фразы, используемой стандартом, временная принадлежность является свойством выражения, а не объекта. Если вы не хотите читать & quot; сохраняется до & quot; как "сохраняется по меньшей мере до". Но тогда вы потеряете детерминированное разрушение времен, что ИМО еще хуже. – Ben Voigt 8 August 2011 в 22:05
  • 5
    @Ben Voigt В словаре стандартного, объекты являются временными или нет; выражения - значения или lvalues. В контексте, где требуется объект (например, инициализация ссылки), выражение rvalue приведет к созданию временного объекта. Ссылка инициализируется выражением (lvalue или rvalue), что приводит к тому, что он связан с объектом (временным или нет). Ссылка, инициализированная выражением rvalue, привязана к временному; эта ссылка, используемая в выражении, является значением lvalue, относящимся к временному объекту. – James Kanze 9 August 2011 в 10:17

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

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

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

  {A ();  }  

продлится до следующего ; , а не для окружающего его блока. [D0] Demo . Во втором случае, когда вы это сделаете,

  B b ((A ()));   

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

1
ответ дан iammilind 16 August 2018 в 00:28
поделиться

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

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

2
ответ дан Luchian Grigore 16 August 2018 в 00:28
поделиться
  • 1
    На самом деле, нет. Когда точно заканчивается жизнь А? После конструктора Б? Если это так, еще один парень имел тот же ответ, но через некоторое время удалил свой ответ. – Arunmu 4 August 2011 в 07:25
  • 2
    Это не отвечает на мой вопрос. Я также связываю константную ссылку (временную) с другой ссылкой на константу (член), но временное уничтожается раньше. Я специально хочу знать, что это невозможно? (Что касается записи, из вывода я могу интерпретировать порядок разрушений объектов, на самом деле любой может объяснить это. Вопрос в том, почему объекты разрушаются в этом порядке?) – Nawaz 4 August 2011 в 07:25

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

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

  B (const A & amp; a_): a (  a_) {std :: cout & lt;  "B ()" & lt;  станд :: епсИ;  }  

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

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

  B (): a (A ()) {std :: cout & lt  ; & Lt;  "B ()" & lt;  станд :: епсИ;  }  

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

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

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

  B b ((A ()));  // нужны дополнительные скобки!   

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

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

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

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

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

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

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

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

Агрегировать временную инициализацию

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

  struct A {A () {std :: cout & lt;  "A ()" & lt;  станд :: епсИ;  } ~ A () {std :: cout & lt;  "~ A ()" & lt;  станд :: епсИ;  }};  struct B {const A & amp; a;  ~ B () {std :: cout & lt;  "~ B ()" & lt;  станд :: епсИ;  }};  int main () {const B & amp; b = B {A ()};  std :: cout & lt;  "-----" & lt;  станд :: епсИ;  }  

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

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

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

В std :: initializer_list & lt; T & gt; инициализация

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

  struct C {std :: initializer_list & lt; B & gt;  б;  ~ C () {std :: cout & lt;  "~ C ()" & lt;  станд :: епсИ;  }};  int main () {const C & amp; c = C {{{A ()}, {A ()}}};  std :: cout & lt;  "-----" & lt;  станд :: епсИ;  }  

Производится с стволом Clang:

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

и с g ++ trunk:

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

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

  A () A () ----- ~ C () ~ B () ~ A () ~ B () ~ A ()   
4
ответ дан Richard Smith 16 August 2018 в 00:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: