Почему принимает адрес временного незаконного?
Я знаю, что код, написанный ниже, является незаконным
void doSomething(std::string *s){}
int main()
{
doSomething(&std::string("Hello World"));
return 0;
}
Причина в том, что нам не разрешается принимать адрес временного объекта. Но мой вопрос: ПОЧЕМУ?
Рассмотрим следующий код
class empty{};
int main()
{
empty x = empty(); //most compilers would elide the temporary
return 0;
}
В принятом ответе here упоминается
"обычно компилятор рассматривает временную и копию, сконструированную как два объекта, которые расположены в одном и том же месте памяти и избегают копирования."
В соответствии с утверждением можно сделать вывод о том, что временное существовало в некоторой ячейке памяти (следовательно, его адрес мог быть взят), и компилятор решил исключить временный объект, создав объект на месте в том же месте, где временно присутствовал.
Это противоречит тому, что адрес временного не может быть принят?
Я также хотел бы знать, как реализована оптимизация возвращаемого значения. Может ли кто-нибудь предоставить ссылку или статью, связанную с реализацией RVO?
Ответы
Ответ 1
&std::string("Hello World")
Проблема с этим заключается не в том, что std::string("Hello World")
дает временный объект. Проблема заключается в том, что выражение std::string("Hello World")
является выражением rvalue, которое относится к временному объекту.
Вы не можете взять адрес rvalue, потому что не все rvalues имеют адреса (а не все rvalues - объекты). Рассмотрим следующее:
42
Это целочисленный литерал, который является основным выражением и значением r. Это не объект, и он (скорее всего) не имеет адреса. &42
является бессмысленным.
Да, rvalue может ссылаться на объект, как в первом примере. Проблема в том, что не все значения r относятся к объектам.
Ответ 2
Длинный ответ:
[...] можно сделать вывод, что временное существовало в некоторой ячейке памяти
По определению:
- "временный" означает: временный объект
- объект занимает область хранения
- все объекты имеют адрес
Таким образом, не требуется очень подробное доказательство того, что у временного есть адрес. Это по определению.
OTOH, вы не просто выбираете адрес, вы используете встроенный адрес оператора. Спецификация встроенного адреса оператора говорит, что вы должны иметь lvalue:
-
&std::string()
плохо сформирован, потому что std::string()
является rvalue. Во время выполнения эта оценка этого выражения создает временный объект как побочный эффект, а выражение дает значение r, относящееся к созданному объекту.
-
&(std::string() = "Hello World")
хорошо сформирован, потому что std::string() = "Hello World"
является lvalue. По определению, lvalue ссылается на объект. Объект, на который ссылается это значение, является тем же временным
Краткий ответ:
Это правило. Ему не нужны (неправильные, необоснованные) оправдания, которые некоторые люди составляют.
Ответ 3
$5.3.1/2 - "Результат унарного и оператора - указатель на его операнд. Операнд должен быть lvalue или qualid."
Выражения, такие как
99
A() // where A is a user defined class with an accessible
// and unambiguous default constructor
- все Rvalues.
$3.10/2 - "Значение l относится к объекта или функции. Некоторое значение выражения - классы класса или cv-class class type - также ссылаются на objects.47)"
И это мое предположение: хотя Rvalues может занимать память (например, в случае объектов), стандарт С++ не позволяет принимать свой адрес для поддержания единообразия со встроенными типами
Здесь что-то интересное:
void f(const double &dbl){
cout << &dbl;
}
int main(){
f(42);
}
Выражение "42" - это Rvalue, которое привязано к "ссылке на const double" и, следовательно, создает временный объект типа double. Адрес этого временного объекта можно взять внутри функции "f". Но обратите внимание, что внутри "f" это не временный или Rvalue. В тот момент, когда ему задано такое имя, как 'dbl', оно рассматривается как выражение Lvalue внутри 'f'.
Вот что-то на NRVO (похоже)
Ответ 4
Временный пример C + + rvalue. Предполагается, что он чисто представляет значение в своем типе. Например, если вы пишете 42
в двух разных местах вашей программы, экземпляры 42
неотличимы, несмотря на то, что они, вероятно, находятся в разных местах в разное время. Причина, по которой вы не можете принять адрес, заключается в том, что вам нужно что-то сделать, чтобы указать, что должен быть адрес, потому что иначе понятие адреса семантически нечисто и неинтуитивно.
Требование к языку, что вы "что-то делаете", несколько условно, но делает программы на С++ более чистыми. Это сосало бы, если бы люди привыкли принимать адреса времен. Понятие адреса тесно связано с понятием жизни, поэтому имеет смысл сделать "мгновенные" значения без адресов. Тем не менее, если вы будете осторожны, вы можете получить адрес и использовать его в течение срока службы, который позволяет стандарт.
В других ответах есть несколько ошибок:
-
"Вы не можете принимать адрес rvalue, потому что не все rvalues имеют адреса". - Не все lvalues имеют адреса. Типичная локальная переменная типа int
, которая участвует в простом цикле и впоследствии не используется, скорее всего, будет назначена регистру, но не будет содержать стек. Нет места памяти не означает адрес. Компилятор присваивает ему место памяти, если вы берете его адрес. То же самое относится к значениям r, которые могут быть привязаны к const
ссылкам. "Адрес 42
" может быть получен как таковой:
int const *fortytwo_p = & static_cast<int const &>( 42 );
Конечно, после ;
адрес недействителен, потому что временные временные, и это, скорее всего, приведет к дополнительным инструкциям, так как машина может бессмысленно хранить 42 в стеке.
Стоит отметить, что С++ 0x очищает понятия, определяя значение prvalue как значение выражения, независимо от хранилища, и glvalue, которое является местом хранения независимо от его содержимого. Это, вероятно, было целью стандарта С++ 03.
-
"Тогда вы можете изменить временное, что бессмысленно". - Фактически временные эффекты с побочными эффектами полезны для модификации. Рассмотрим это:
if ( istringstream( "42" ) >> my_int )
Это приятная идиома для преобразования числа и проверки того, что преобразование выполнено успешно. Это связано с созданием временного вызова функции мутации на нем, а затем его уничтожения. Далеко не бессмысленно.
Ответ 5
Это можно сделать, но как только временное перестанет существовать, у вас есть висячий указатель.
ИЗМЕНИТЬ
Для downvoters:
const std::string &s = std::string("h");
&s;
является законным. s
является ссылкой на временную. Следовательно, можно принять временный адрес объекта.
EDIT2
Связанные ссылки - это псевдонимы, к которым они привязаны. Следовательно, ссылка на временное - это другое имя для этого временного. Следовательно, выполняется второе утверждение в предыдущем абзаце.
Вопрос OP - это временные (с точки зрения слов, которые он использует), а его пример - значения r. Это две разные концепции.
Ответ 6
Одна из причин заключается в том, что ваш пример предоставит методу доступ на запись к временному, что бессмысленно.
Цитата, которую вы указали, не относится к этой ситуации, это определенная оптимизация, разрешенная в деклараторах с инициализаторами.
Ответ 7
Почему занимает адрес временного незаконного?
Объем временных переменных ограничен некоторым конкретным способом или некоторым блоком, как только вызов метода возвращает временные переменные удаляются из памяти, поэтому, если мы вернем адрес переменной, которая больше не существует в памяти это не имеет смысла. Тем не менее адрес действителен, но этот адрес теперь может содержать некоторое количество мусора.