Является ли время жизни ссылки расширенным?
Я хочу передать ссылку в функцию. Этот код не работает, как я ожидал:
struct A {
};
void foo(A& a) {
// do something with a
}
int main(int, char**) {
foo(A());
}
Я получаю ошибку компиляции
неверная инициализация неконстантной ссылки типа A&
из rvalue типа A
Но когда я просто добавляю метод A& ref()
в A
, как показано ниже, и вызываю его, прежде чем передавать его, кажется, я могу использовать A
. При отладке объект A
уничтожается после вызова foo()
:
struct A {
A& ref() {
return *this;
}
};
void foo(A& a) {
// do something with a
}
int main(int, char**) {
foo(A().ref());
}
Является ли этот действующий код стандартным? Вызывает ли вызов ref()
время жизни объекта до тех пор, пока foo()
не вернется?
Ответы
Ответ 1
Ваш код отлично работает.
В этой строке
foo(A().ref());
Экземпляр временного A
живет до конца инструкции (;
).
Для чего безопасно передать A&
, возвращенный с ref()
в foo
(пока foo
не сохраняет его).
ref()
сам по себе не продлевает время жизни, но помогает, возвращая ссылку lvalue.
Что происходит в случае foo(A());
? Здесь временное значение передается как rvalue. И в С++ rvalue не связывается с неконстантными ссылками lvalue (даже в С++ 11, ссылка rvalue не связывается с неконстантными ссылками lvalue).
Из этой статьи в блоге Visual С++ о ссылках rvalue:
... С++ не хочет, чтобы вы случайно изменяли временные, но напрямую вызов функции non-const-члена на изменяемом rvalue является явным, поэтому это позволило...
Ответ 2
A()
создает временный объект типа A
. Объект существует до конца полного выражения, в котором он создается. Проблема в вашем исходном коде - не время жизни этого временного; это то, что функция принимает свой аргумент как неконстантную ссылку, и вам не разрешается передавать временный объект как неконстантную ссылку. Самое простое изменение для foo
, чтобы принять его аргумент по ссылке const, если это соответствует тому, что делает функция:
void foo(const A&);
int main() {
foo(A());
}
Ответ 3
В этом вопросе есть несколько вопросов. Я попытаюсь обратиться ко всем из них:
Во-первых, вы не можете передать временное (prvalue) типа A
в функцию, принимающую A&
, потому что ссылки не-const lvalue не могут связываться с rvalues. Это языковое ограничение. Если вы хотите иметь возможность пройти временное, вам нужно либо взять параметр типа A&&
, либо тип A const&
- последний, поскольку временные объекты могут связываться с константными ссылками lvalue.
Является ли этот действующий код стандартным? Вызывает ли вызов ref()
магическое время жизни объекта до тех пор, пока foo()
не вернется?
В вашей программе вообще нет продления жизни. Из [class.temp]:
Существует три контекста, в которых временные объекты уничтожаются в другой точке, чем конец полного выражения. Первый контекст - это когда конструктор по умолчанию вызывается для инициализации элемента массива с помощью нет соответствующего инициализатора (8.6). Второй контекст - это когда конструктор копирования вызывается для копирования элемента массива, в то время как весь массив копируется (5.1.5, 12.8). [...] Третий контекст - это когда привязка привязана к временному.
Ни один из этих контекстов не применяется. Мы никогда не связываем ссылку на временный код. ref()
привязывает *this
к A&
, но *this
не является временным, и затем эта ссылка просто передается в foo()
.
Рассмотрим этот вариант вашей программы:
#include <iostream>
struct A {
A& ref() { return *this; }
~A() { std::cout << "~A()\n"; }
};
int main() {
auto& foo = A().ref();
std::cout << "----\n";
}
который печатает
~A()
----
иллюстрирующий отсутствие продления срока службы.
Если вместо привязки результата ref()
к ссылке мы привязали элемент:
#include <iostream>
struct A {
A& ref() { return *this; }
int x;
~A() { std::cout << "~A()\n"; }
};
int main() {
auto&& foo = A().x;
std::cout << "----\n";
}
то мы фактически привязываем временную ссылку и применяем третий контекст - время жизни полного объекта подобъекта, которому привязана ссылка, сохраняется для времени жизни ссылки. Таким образом, этот код печатает:
----
~A()