продление срока службы временных - PullRequest
14 голосов
/ 12 января 2011

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

const Foo& a = function_returning_Foo_by_value();

но не это

Foo& a = function_returning_Foo_by_value();

Что может пойти не так во второй строке (что не могло бы пойти не так в первой строке)?

Ответы [ 4 ]

4 голосов
/ 12 января 2011

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3 голосов
/ 12 января 2011

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

3 голосов
/ 12 января 2011

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

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

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

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

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

bool validate_the_cat(const string&);

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

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

0 голосов
/ 12 января 2011

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

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

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

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

...