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

Продление срока службы временных

Каково обоснование дизайна, позволяющее этому

const Foo& a = function_returning_Foo_by_value();

но не этот

Foo& a = function_returning_Foo_by_value();

?

Что может произойти неправильно во второй строке (что в первой строке не пойдет не так)?

4b9b3361

Ответ 1

Я отвечу на ваш вопрос... наоборот.

Почему они разрешили Foo const& foo = fooByValue(); начать с?

Это облегчает жизнь (несколько), но вводит потенциальное поведение undefined повсюду.

Foo const& fooByReference()
{
  return fooByValue(); // error: returning a reference to a temporary
}

Это, очевидно, неправильно, и действительно, компилятор будет покорно сообщать об этом. Согласно комментарию Томалака: стандарт не санкционирован, но хорошие компиляторы должны сообщать об этом. Clang, gcc и MSVC. Я думаю, что Комо и ICC тоже.

Foo const& fooByIndirectReference()
{
  Foo const& foo = fooByValue(); // OK, you're allowed to bind a temporary
  return foo;                    // Generally accepted
}

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

Я поднял ошибку на Клэнге, и Аргирис смог диагностировать этот случай (на самом деле: p).

Foo const& fooForwarder(Foo const&); // out of line implementation which forwards
                                     // the argument

Foo const& fooByVeryIndirectReference()
{
  return fooForwarder(fooByValue());
}

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

Проблема здесь в том, что реализация fooForwarder полностью прекрасна по стандарту, но при этом она создает поведение undefined в своем вызывающем абоненте.

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

Единственное решение, которое я могу понять (помимо WPA), - это временное решение: всякий раз, когда временный ограничивается ссылкой, вам нужно убедиться, что возвращаемая ссылка не имеет одного и того же адреса... а затем что? assert? вызывать исключение? И поскольку это только решение времени выполнения, это явно неудовлетворительно.

Идея привязки временного к ссылке хрупкая.

Ответ 2

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

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

struct Foo {};
bool CreateFoo( Foo*& result ) { result = new Foo(); return true; }

struct SpecialFoo : Foo {};
SpecialFoo* p;
if (CreateFoo(p)) { /* DUDE, WHERE MY OBJECT! */ }

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

bool validate_the_cat(const string&);

string thing[3];
validate_the_cat(thing[1] + thing[2]);

Обратите внимание, что в этом случае не требуется расширение продолжительности жизни.

Ответ 3

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

Ответ 4

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

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

std::string val;
some_func_that_returns_a_string().swap( val );

Иногда это может быть очень полезно.