Каково обоснование исключений временного расширения времени жизни объекта при привязке к ссылке?

в 12.2 стандарта C++11:

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

  1. временная граница к ссылочному члену в ctor-инициализаторе конструктора (12.6.2) сохраняется до завершения работы конструктора.

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

  3. продолжительность жизни временной привязки к возвращаемому значению в функции return заявление (6.6.3) не продлевается; временное уничтожается на конец полного выражения в операторе return.

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

и есть пример последнего случая в стандарте:

struct S {
  int mi; 
  const std::pair<int,int>& mp;
}; 
S a { 1,{2,3} };  // No problem.
S* p = new S{ 1, {2,3} };  // Creates dangling reference

ко мне, 2. and 3. имеет смысл и легко договориться. Но в чем причина bebind 1. and 4.? Этот пример кажется мне просто злым.

3 ответов


как и многие вещи в C и C++, я думаю, что это сводится к тому, что может быть разумно (эффективно) реализовать.

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

  struct S {
    int mi;
    const std::pair<int,int>& mp;
  };

  // Case 1:
  std::pair<int,int> tmp{ 2, 3 };
  S a { 1, tmp };

компилятор может легко продлить срок службы tmp временные достаточно долго, чтобы держите" S " действительным, потому что мы знаем, что "S" будет уничтожен до конца функции.

но это не работает в случае "new S":

  struct S {
    int mi;
    const std::pair<int,int>& mp;
  };

  // Case 2:
  std::pair<int,int> tmp{ 2, 3 };
  // Whoops, this heap object will outlive the stack-allocated
  // temporary!
  S* p = new S{ 1, tmp };

чтобы избежать висячей ссылки, нам нужно было бы выделить временное в куче вместо стека, что-то вроде:

   // Case 2a -- compiler tries to be clever?
   // Note that the compiler won't actually do this.
   std::pair<int,int> tmp = new std::pair<int,int>{ 2, 3 };
   S* p = new S{ 1, tmp };

но тогда соответствующие delete p нужно, чтобы освободить эту память! Это совершенно противоречит поведению ссылок и нарушит все, что использует normal справочная семантика:

  // No way to implement this that satisfies case 2a but doesn't
  // break normal reference semantics.
  delete p;

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

предупреждение: @Potatoswatter отмечает ниже, что это, похоже, не реализуется последовательно в компиляторах C++ и, следовательно, не является переносимым в лучшем случае на данный момент. См. его пример для того, как Clang не делает то, что стандарт кажется мандат здесь. Он также говорит, что ситуация "может быть более ужасной" - я не знаю точно, что это означает, но похоже, что на практике этот случай в C++ имеет некоторую неопределенность.


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

при вызове функции она расширяется до конца текущей строки. Это достаточно долго, и легко определить.

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

на new выражение, точка разрушения не может быть статически определена в точке создания. Это когда delete происходит. Если бы мы хотели delete чтобы (иногда) уничтожить временное, тогда наша ссылочная "двоичная" реализация должна быть более сложной, чем указатель, а не меньше или равно. Иногда он будет владеть указанными данными, а иногда нет. Так что это указатель, плюс bool. И в C++ вы не платите за то, что не используете.

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


как долго вы хотите, чтобы временный объект к последнему? Он должен быть где-то выделен.

Он не может быть в куче, потому что он будет протекать; нет применимого автоматического управления памятью. Он не может быть статичным, потому что его может быть несколько. Он должен быть в стопке. Затем он либо длится до конца выражения, либо до конца функции.

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

Я не уверен, почему вы говорите, что это не проблема:

S a { 1,{2,3} };  // No problem.

висячая ссылка такая же, используете ли вы new.

инструментирование вашей программы и запуск ее в Clang производит следующее результаты:

#include <iostream>

struct noisy {
    int n;
    ~noisy() { std::cout << "destroy " << n << "\n"; }
};

struct s {
    noisy const & r;
};

int main() {
    std::cout << "create 1 on stack\n";
    s a {noisy{ 1 }};  // Temporary created and destroyed.

    std::cout << "create 2 on heap\n";
    s* p = new s{noisy{ 2 }};  // Creates dangling reference
}

create 1 on stack
destroy 1
create 2 on heap
destroy 2

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

на самом деле я уверен, что это предмет известный дефект в стандарте, но у меня нет времени вникать сейчас...