Подтвердить что ты не робот

В синтаксисе лямбда С++ 11 выделены кучи?

С++ 11 lambdas великолепны!

Но не хватает одной вещи, которая заключается в том, как безопасно обрабатывать изменяемые данные.

Ниже перечислены плохие счета после первого счета:

#include <cstdio>
#include <functional>
#include <memory>

std::function<int(void)> f1()
{
    int k = 121;
    return std::function<int(void)>([&]{return k++;});
}

int main()
{
    int j = 50;
    auto g = f1();
    printf("%d\n", g());
    printf("%d\n", g());
    printf("%d\n", g());
    printf("%d\n", g());
}

дает

$ g++-4.5 -std=c++0x -o test test.cpp && ./test
121
8365280
8365280
8365280

Причина в том, что после возврата f1() k выходит за пределы области видимости, но все еще находится в стеке. Итак, в первый раз g() выполняется k отлично, но после этого стек поврежден и k теряет свое значение.

Таким образом, единственный способ, которым я смог сделать безопасное возвратное закрытие в С++ 11, - явно выделить закрытые переменные в куче:

std::function<int(void)> f2()
{
    int k = 121;
    std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
    return std::function<int(void)>([=]{return (*o)++;});
}

int main()
{
    int j = 50;
auto g = f2();
    printf("%d\n", g());
    printf("%d\n", g());
    printf("%d\n", g());
    printf("%d\n", g());
}

Здесь [=] используется для обеспечения копирования общего указателя, а не ссылки, чтобы обработка памяти выполнялась правильно: выделенная кучей копия k должна быть освобождена при выходе из сгенерированной функции g объема. Результат по желанию,

$ g++-4.5 -std=c++0x -o test test.cpp && ./test
121
122
123
124

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

std::function<int(void)> f3()
{
    int k = 121;
    std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
    int &p = *o;
    return std::function<int(void)>([&]{return p++;});
}

Собственно, это странно дает мне,

$ g++-4.5 -std=c++0x -o test test.cpp && ./test
0
1
2
3

Любая идея, почему? Может быть, не вежливо взять ссылку на общий указатель, теперь, когда я думаю об этом, так как это не отслеживаемая ссылка. Я обнаружил, что перемещение ссылки внутрь лямбда вызывает сбой,

std::function<int(void)> f4()
{
    int k = 121;
std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
    return std::function<int(void)>([&]{int &p = *o; return p++;});
}

даяние,

g++-4.5 -std=c++0x -o test test.cpp && ./test
156565552
/bin/bash: line 1: 25219 Segmentation fault      ./test

В любом случае было бы неплохо, если бы был способ автоматически сделать безопасное возвратное закрытие посредством распределения кучи. Например, если была альтернатива [=] и [&], которая указывала, что переменные должны быть выделены в кучу и ссылаться через ссылки на общие указатели. Моя первоначальная мысль, когда я узнал о std::function, заключалась в том, что она создает объект, инкапсулирующий замыкание, поэтому он может обеспечить хранение для среды закрытия, но мои эксперименты показывают, что это, похоже, не помогает.

Я думаю, что безопасно возвратные закрытия в С++ 11 будут иметь первостепенное значение для их использования, кто-нибудь знает, как это можно сделать более элегантно?

4b9b3361

Ответ 1

В f1 вы получаете поведение undefined по причине, которую вы говорите; lambda содержит ссылку на локальную переменную, а после возврата функции ссылка больше недействительна. Чтобы обойти это, вам не нужно выделять кучу, вам просто нужно объявить, что зафиксированные значения изменяемы:

int k = 121;
return std::function<int(void)>([=]() mutable {return k++;});

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


В f3 ничего не поддерживается копией общего указателя, поэтому память освобождается и доступ к ней дает поведение undefined. Вы можете исправить это, явно захватив общий указатель по значению и по-прежнему захватить указатель на int по ссылке.

std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
int &p = *o;
return std::function<int(void)>([&p,o]{return p++;});

f4 снова работает undefined, потому что вы снова захватываете ссылку на локальную переменную o. Вы должны просто захватить по значению, но затем создать свой int &p внутри лямбды, чтобы получить синтаксис, который вы хотите.

std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
return std::function<int(void)>([o]() -> int {int &p = *o; return p++;});

Обратите внимание, что при добавлении второго оператора С++ 11 больше не нужно опускать возвращаемый тип. (clang и я предполагаю, что gcc имеет расширение, которое позволяет выводить тип вывода даже с несколькими операторами, но вы должны получить предупреждение как минимум.)

Ответ 2

Вот мой тестовый код. Он использует рекурсивную функцию для сравнения адреса лямбда-параметров с другими переменными на основе стека.

#include <stdio.h> 
#include <functional> 

void fun2( std::function<void()> callback ) { 
    (callback)(); 
} 

void fun1(int n) { 
    if(n <= 0) return; 
    printf("stack address = %p, ", &n); 

    fun2([n]() { 
        printf("capture address = %p\n", &n); 
        fun1(n - 1); 
    }); 
} 

int main() { 
    fun1(200); 
    return 0; 
}

Скомпилируйте код с помощью mingw64 и запустите на Win7, выведите

stack address = 000000000022F1E0, capture address = 00000000002F6D20
stack address = 000000000022F0C0, capture address = 00000000002F6D40
stack address = 000000000022EFA0, capture address = 00000000002F6D60
stack address = 000000000022EE80, capture address = 00000000002F6D80
stack address = 000000000022ED60, capture address = 00000000002F6DA0
stack address = 000000000022EC40, capture address = 00000000002F6DC0
stack address = 000000000022EB20, capture address = 00000000007A7810
stack address = 000000000022EA00, capture address = 00000000007A7820
stack address = 000000000022E8E0, capture address = 00000000007A7830
stack address = 000000000022E7C0, capture address = 00000000007A7840

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

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