C++11 лямбда-реализация и модель памяти


Я хотел бы получить некоторую информацию о том, как правильно думать о закрытиях C++11 и std::function С точки зрения того, как они реализованы и как обрабатывается память.

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

поэтому я хотел бы лучше понять, когда использовать или не использовать лямбды C++.

мое текущее понимание заключается в том, что лямбда без захваченного закрытия точно так же, как обратный вызов C. Однако, когда среда захватывается либо по значению, либо по ссылке, в стеке создается анонимный объект. Когда значение-закрытие должно быть возвращено из функции, один обертывает его в std::function. Что происходит с памятью закрытия в это дело? Он копируется из стека в кучу? Это освобождается всякий раз, когда std::function освобождается, т. е. это ссылка расценено как std::shared_ptr?

Я полагаю, что в системе реального времени я мог бы настроить цепочку лямбда-функций, передав B в качестве аргумента продолжения A, так что конвейер обработки это. В этом случае закрытие A и B будет выделено один раз. Хотя я не уверен, будут ли они выделены в стеке или куче. Однако в вообще это кажется безопасным для использования в системах реального времени. С другой стороны, если B создает некоторую лямбда-функцию C, которую он возвращает, то память для C будет выделяться и освобождаться повторно, что было бы неприемлемо для использования в реальном времени.

в псевдокоде, цикл DSP, который, я думаю, будет безопасным в режиме реального времени. Я хочу выполнить обработку блока A, а затем B, где A вызывает его аргумент. Обе эти функции возвращают std::function объекты, так f будет std::function объект, где его окружение хранится в куче:

auto f = A(B);  // A returns a function which calls B
                // Memory for the function returned by A is on the heap?
                // Note that A and B may maintain a state
                // via mutable value-closure!
for (t=0; t<1000; t++) {
    y = f(t)
}

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

for (t=0; t<1000; t++) {
    y = A(B)(t);
}

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

freq = 220;
A = 2;
for (t=0; t<1000; t++) {
    y = [=](int t){ return sin(t*freq)*A; }
}

в последнем случае закрытия создается на каждой итерации цикла, но в отличие от предыдущего примера это дешево, потому что это просто как вызов функции, без выделений сделаны. Более того, мне интересно, если компилятор может "поднять" закрытие и сделать встроенные оптимизации.

это правильно? Спасибо.

1 78

1 ответ:

мое текущее понимание заключается в том, что лямбда без захваченного закрытия точно так же, как обратный вызов C. Однако, когда среда захватывается либо по значению, либо по ссылке, в стеке создается анонимный объект.

нет, это всегда объект C++ с неизвестным типом, созданный в стеке. Лямбда без захвата может быть преобразовать в указатель функции (хотя подходит ли он для соглашений о вызовах C зависит от реализации), но это не значит, что и указатель на функцию.

когда значение-закрытие должно быть возвращено из функции, один обертывает его в std:: function. Что происходит с памятью закрытия в этом случае?

лямбда-это не что-то особенное в C++11. Это такой же объект, как и любой другой. Лямбда-выражение приводит к временному, который может быть использован для инициализации переменной на стек:

auto lamb = []() {return 5;};

lamb - это объект стека. Он имеет конструктор и деструктор. И он будет следовать всем правилам C++ для этого. Тип lamb будет содержать захваченные значения / ссылки; они будут членами этого объекта, как и любые другие члены объекта любого другого типа.

вы можете дать его std::function:

auto func_lamb = std::function<int()>(lamb);

в этом случае он получит скопировать стоимостью lamb. Если lamb захватили что-нибудь по значению, было бы две копии этих значений; в lamb и один в func_lamb.

когда текущая область заканчивается,func_lamb будет уничтожен, а затем lamb, согласно правилам очистки переменных стека.

вы могли бы так же легко выделить один в куче:

auto func_lamb_ptr = new std::function<int()>(lamb);

именно там, где память на содержание std::function goes зависит от реализации, но тип-стирание, используемое std::function как правило, требует по крайней мере одно выделение памяти. Вот почему std::functionконструктор может принимать распределитель.

освобождается ли он всякий раз, когда функция std::освобождается, т. е. считается ли она ссылочной, как std::shared_ptr?

std::function хранит a скопировать его содержание. Как и практически все стандартные библиотеки типа C++,function использует значение семантики. Таким образом, он копируется; когда он копируется, новый function объект полностью отделен. Он также подвижен, поэтому любые внутренние распределения могут быть переданы соответствующим образом без необходимости дополнительного выделения и копирования.

таким образом, нет необходимости в подсчете ссылок.

все остальное вы заявляете правильно, предполагая, что" выделение памяти "приравнивается к"плохо использовать в коде реального времени".