Может ли лямбда безопасно вернуть адрес скопированной переменной?
Учитывая следующий пример кода:
int main()
{
int i;
auto f = [=]()mutable->int*
{
return &i;
};
return 0;
}
- g++ v.4.8.1 предупреждает, что "адрес локальной переменной" я вернулся ".
- Clang v.3.2 (MacOS Clang) предупреждает, что "адрес памяти стека
связанные с локальной переменной "i".
- Ни VS2012, ни VS2013 RC не предупреждают о чем-либо.
Мое понимание lambdas заключается в том, что компилятор будет генерировать класс функтора. Этот класс функторов будет содержать члены для всех скопированных переменных (i
в примере). Я считаю, что в контексте моего кода, пока существует f
, можно вернуть адрес одного из его членов. Мне кажется, что все компиляторы ошибались. Я думаю, что предупреждение об использовании адреса f
member i
после f
выходит за рамки допустимо, но предупреждения о "локальной переменной i" неверны/вводятся в заблуждение. Я прав?
Ответы
Ответ 1
Да, ты прав. Оператор &
применяется к элементу, а не локальному объекту.
Демонстрация прост: просто измените свой пример, чтобы вывести адреса.
#include <iostream>
int main() {
int i;
std::cout << & i << '\n';
std::cout << [=]() mutable -> int * {
return & i;
} () << '\n';
}
http://ideone.com/OqsDyg
Кстати, это компилируется без предупреждений в -Wall
в GCC 4.9.
Ответ 2
Некоторая терминология:
-
=
или &
внутри [&](){ /*..*/ }
называется значением по умолчанию для захвата.
- odr-использование переменной примерно означает, что переменная не появляется в выражении с отбрасыванием-значением (например,
(void)some_variable
или int x = some_variable, 5;
), и оно не встречается в постоянном выражении.
- составной оператор - это "функциональный блок"
{
statement }
- имя переменной является id-выражением
[expr.prim.lambda]/3
Тип лямбда-выражения (который также является типом объекта замыкания) - это уникальный, неназванный тип неединичного класса, называемый типом замыкания, свойства которого описаны ниже.
/11
Если лямбда-выражение имеет связанный с захватом-умолчанию и его составной оператор odr-uses (3.2) this
или переменную с автоматической продолжительностью хранения, а объект, использующий odr, явно не захвачен, тогда odr- используемый объект считается неявным захватом;
Следовательно, i
неявно фиксируется.
/14
Объект захватывается копией, если он неявно захвачен, а значение по умолчанию - =
или если оно явно захвачено с захватом, который не включает &
. Для каждого объекта, захваченного копией, в типе закрытия объявляется неназванный нестатический элемент данных.
В типе замыкания есть нестатический элемент данных (типа int
).
/17
Каждое id-выражение, которое является неприемлемым (3.2) объекта, захваченного копией, преобразуется в доступ к соответствующему неименованному элементу данных типа замыкания.
Нам даже не нужно это интерпретировать, поскольку этот параграф дает нам пример, очень похожий на OP:
void f(const int*);
void g() {
const int N = 10;
[=] {
int arr[N]; // OK: not an odr-use, refers to automatic variable
f(&N); // OK: causes N to be captured; &N points to the
// corresponding member of the closure type
};
}
Если мы применим это к OP примеру, мы увидим, что &i
относится к внутреннему нестатическому элементу данных типа замыкания. Независимо от того, соответствует ли диагностическое сообщение, в стандарте не указано;)