Я пытаюсь понять / уточнить код кода, который генерируется, когда перехваты передаются лямбдам, особенно в обобщенных перехватах инициализации, добавленных в C ++ 14.
Дайте следующие примеры кода, перечисленные ниже, это мое текущее понимание того, что сгенерирует компилятор.
Случай 1: захват по значению / захват по умолчанию по значению
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
Будет соответствовать:
class __some_compiler_generated_name {
public:
__some_compiler_generated_name(int x) : __x{x}{}
void operator()() const { std::cout << __x << std::endl;}
private:
int __x;
};
Таким образом, существует несколько копий, одну для копирования в параметр конструктора и одну для копирования в элемент, что будет дорого для таких типов, как vector и т. Д.
Случай 2: захват по ссылке / захват по умолчанию по ссылке
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
Будет соответствовать:
class __some_compiler_generated_name {
public:
__some_compiler_generated_name(int& x) : x_{x}{}
void operator()() const { std::cout << x << std::endl;}
private:
int& x_;
};
Параметр является ссылкой, а член является ссылкой, поэтому нет копий. Хорошо подходит для таких типов, как вектор и т. Д.
Случай 3:
Обобщенный захват инициализации
auto lambda = [x = 33]() { std::cout << x << std::endl; };
Я понимаю, что это похоже на случай 1 в том смысле, что он копируется в член.
Я предполагаю, что компилятор генерирует код, похожий на ...
class __some_compiler_generated_name {
public:
__some_compiler_generated_name() : __x{33}{}
void operator()() const { std::cout << __x << std::endl;}
private:
int __x;
};
Также, если у меня есть следующее:
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
Как будет выглядеть конструктор? Это также перемещает это в участника?
Ответы:
На этот вопрос нельзя ответить полностью в коде. Возможно, вы сможете написать несколько «эквивалентный» код, но стандарт не указан таким образом.
С этим из пути, давайте погрузимся в
[expr.prim.lambda]
. Первое, что нужно отметить, это то, что конструкторы упоминаются только в[expr.prim.lambda.closure]/13
:Таким образом, сразу же должно быть ясно, что конструкторы формально не определяют способ захвата объектов. Вы можете подойти довольно близко (см. Ответ cppinsights.io), но детали отличаются (обратите внимание, что код в этом ответе для случая 4 не компилируется).
Это основные стандартные положения, необходимые для обсуждения варианта 1:
[expr.prim.lambda.capture]/10
[expr.prim.lambda.capture]/11
[expr.prim.lambda.capture]/15
Давайте применим это к вашему случаю 1:
Тип закрытия этой лямбды будет иметь неназванный нестатический элемент данных (давайте назовем его
__x
) типаint
(такx
как не является ни ссылкой, ни функцией), и доступыx
внутри тела лямбда преобразуются в доступы к__x
. Когда мы оцениваем лямбда-выражение (т. Е. При присваиванииlambda
), мы выполняем прямую инициализацию__x
с помощьюx
.Короче говоря, только одна копия имеет место . Конструктор типа замыкания не задействован, и это невозможно выразить в «нормальном» C ++ (обратите внимание, что тип замыкания также не является агрегатным ).
Сбор ссылок включает в себя
[expr.prim.lambda.capture]/12
:Есть еще один параграф о захвате ссылок, но мы нигде этого не делаем.
Итак, для случая 2:
Мы не знаем, добавлен ли член к типу замыкания.
x
в теле лямбда может просто напрямую ссылаться наx
внешнее. Это зависит от компилятора, чтобы выяснить, и он будет делать это в некоторой форме промежуточного языка (который отличается от компилятора к компилятору), а не исходного преобразования кода C ++.Захваты инициации подробно описаны в
[expr.prim.lambda.capture]/6
:Учитывая это, давайте посмотрим на случай 3:
Как уже говорилось, представьте, что это переменная, созданная
auto x = 33;
и явно захваченная копией. Эта переменная видна только внутри лямбда-тела. Как отмечалось[expr.prim.lambda.capture]/15
ранее, инициализация соответствующего члена типа замыкания (__x
для потомков) осуществляется данным инициализатором при оценке лямбда-выражения.Во избежание сомнений: это не значит, что здесь все инициализируется дважды.
auto x = 33;
«как будто» наследовать семантику простых захватов, и описанная инициализация является модификацией этой семантики. Происходит только одна инициализация.Это также охватывает случай 4:
Член типа замыкания инициализируется,
__p = std::move(unique_ptr_var)
когда вычисляется лямбда-выражение (т.е. когдаl
ему назначается). Доступ кp
лямбда-телу превращается в доступ к__p
.TL; DR: выполняется только минимальное количество копий / инициализаций / ходов (как можно было бы надеяться / ожидать). Я бы предположил, что лямбды не определяются в терминах исходного преобразования (в отличие от другого синтаксического сахара) именно потому , что выражение вещей в терминах конструкторов потребовало бы лишних операций.
Я надеюсь, что это улаживает опасения, выраженные в вопросе :)
источник
Случай 1
[x](){}
: сгенерированный конструктор примет свой аргумент с помощью возможноconst
-качественной ссылки, чтобы избежать ненужных копий:Случай 2
[x&](){}
: Ваши предположения здесь верны,x
передаются и сохраняются по ссылке.Случай 3
[x = 33](){}
: снова правильный,x
инициализируется значением.Случай 4
[p = std::move(unique_ptr_var)]
: конструктор будет выглядеть так:так что да,
unique_ptr_var
это "переместилось" в замыкание. См. Также пункт 32 Скотта Мейера в Effective Modern C ++ («Используйте захват init для перемещения объектов в замыкания»).источник
const
Квалифицированный» Почему?const
здесь не может быть больно из-за некоторой неоднозначности / лучшего соответствия, когда неconst
и т. Д. В любом случае, вы думаете, я должен удалитьconst
?Нет необходимости спекулировать, используя cppinsights.io .
Дело 1:
Код
Компилятор генерирует
Дело 2:
Код
Компилятор генерирует
Дело 3:
Код
Компилятор генерирует
Дело 4 (неофициально):
Кодекс
Компилятор генерирует
И я верю, что этот последний фрагмент кода отвечает на ваш вопрос. Перемещение происходит, но не [технически] в конструкторе.
Самих снимков нет
const
, но вы можете видеть, чтоoperator()
функция есть. Естественно, если вам нужно изменить снимки, вы помечаете лямбду какmutable
.источник