Почему лямбда-код C++11 по умолчанию требует "изменяемого" ключевого слова для захвата по значению?



короткий пример:



#include <iostream>

int main()
{
int n;
[&](){n = 10;}(); // OK
[=]() mutable {n = 20;}(); // OK
// [=](){n = 10;}(); // Error: a by-value capture cannot be modified in a non-mutable lambda
std::cout << n << "n"; // "10"
}


вопрос: Зачем нам нужен mutable ключевое слово? Это сильно отличается от традиционной передачи параметров в именованных функций. В чем причина этого?



у меня создалось впечатление, что весь смысл capture-by-value заключается в том, чтобы позволить пользователю изменить временное-в противном случае мне почти всегда лучше использовать capture-by-reference, не так ли?



какие-нибудь просветления?



(Я использую MSVC2010 by путь. Насколько мне известно, это должно быть стандартом)

609   10  

10 ответов:

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

ваш код почти эквивалентен этому:

#include <iostream>

class unnamed1
{
    int& n;
public:
    unnamed1(int& N) : n(N) {}

    /* OK. Your this is const but you don't modify the "n" reference,
    but the value pointed by it. You wouldn't be able to modify a reference
    anyway even if your operator() was mutable. When you assign a reference
    it will always point to the same var.
    */
    void operator()() const {n = 10;}
};

class unnamed2
{
    int n;
public:
    unnamed2(int N) : n(N) {}

    /* OK. Your this pointer is not const (since your operator() is "mutable" instead of const).
    So you can modify the "n" member. */
    void operator()() {n = 20;}
};

class unnamed3
{
    int n;
public:
    unnamed3(int N) : n(N) {}

    /* BAD. Your this is const so you can't modify the "n" member. */
    void operator()() const {n = 10;}
};

int main()
{
    int n;
    unnamed1 u1(n); u1();    // OK
    unnamed2 u2(n); u2();    // OK
    //unnamed3 u3(n); u3();  // Error
    std::cout << n << "\n";  // "10"
}

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

вы также можете думать обо всех переменных, захваченных внутри [] (явно или неявно) как члены этого класса: копии объектов для [=] или ссылки на объекты для [&]. Они инициализируются, когда вы объявляете свою лямбду, как если бы был скрытый конструктор.

у меня сложилось впечатление, что весь смысл capture-by-value заключается в том, чтобы позволить пользователю изменить временное-в противном случае мне почти всегда лучше использовать capture-by-reference, не так ли?

вопрос в том, это "почти"? Частым вариантом использования является возврат или передача лямбд:

void registerCallback(std::function<void()> f) { /* ... */ }

void doSomething() {
  std::string name = receiveName();
  registerCallback([name]{ /* do something with name */ });
}

думаю, что mutable Не "почти". Я считаю, что "захват по значению", как " позвольте мне использовать его значение после захваченного объекта умирает", а не "позвольте мне изменить его копию". Но, возможно, с этим можно поспорить.

FWIW, Херб Саттер, известный член комитета по стандартизации C++, дает другой ответ на этот вопрос в вопросы корректности и удобства использования лямбда:

рассмотрим этот пример соломенного человека, где программист захватывает локальную переменную с помощью значение и пытается изменить захваченное значение (которое является переменной-членом лямбда-объекта):

int val = 0;
auto x = [=](item e)            // look ma, [=] means explicit copy
            { use(e,++val); };  // error: count is const, need ‘mutable’
auto y = [val](item e)          // darnit, I really can’t get more explicit
            { use(e,++val); };  // same error: count is const, need ‘mutable’

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

его статья о том, почему это должно быть изменено в C++14. Это коротко, хорошо написано, стоит прочитать, если вы хотите знать, "что на уме [члена комитета]" в отношении этой конкретной функции.

посмотреть этот проект, под 5.1.2 [expr.подтянутый.лямбда], раздел 5:

тип замыкания для лямбда-выражения имеет открытый оператор вызова встроенной функции (13.5.4), параметры которого и тип возвращаемого значения описываются параметр-декларации-п. лямбда-выражения и trailingreturn- типа соответственно. этот оператор вызова функции объявляется const (9.3.1) тогда и только тогда, когда лямбда-выражение параметр-объявление-предложение не является затем Мутабельный.

редактировать комментарий litb: Может быть, они думали о захвате по значению, чтобы внешние изменения переменных не отражались внутри лямбды? Ссылки работают в обоих направлениях, так что это мое объяснение. Хотя не знаю, хорошо ли это.

редактировать комментарий kizzx2: В большинстве случаев, когда лямбда должна использоваться в качестве функтора для алгоритмов. Значение по умолчанию constness позволяет использовать его в постоянной среде, как обычно const-квалифицированный функции могут быть использованы там, но не

вы должны думать, что такое тип закрытия функции лямбда. Каждый раз, когда вы объявляете лямбда-выражение, компилятор создает тип закрытия, который является не чем иным, как объявлением безымянного класса с атрибутами (окружающая среда где лямбда-выражение, где объявлено) и вызов функции ::operator() реализованы. При захвате переменной с помощью копировать по значению компилятор создаст новый const атрибут в закрытии введите, поэтому вы не можете изменить его внутри лямбда-выражения, потому что это атрибут" только для чтения", поэтому они называют его"закрытие", потому что каким-то образом вы закрываете свое лямбда-выражение, копируя переменные из верхней области в лямбда-область. При использовании ключевого слова mutable, захваченная сущность станет non-const атрибут вашего типа закрытия. Это то, что заставляет изменения, сделанные в изменяемой переменной, захваченной значением, не распространяться на верхняя область, но держать внутри состояния лямбда. Всегда старайтесь представить себе результирующий тип закрытия вашего лямбда-выражения, что очень помогло мне, и я надеюсь, что это может помочь вам тоже.

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

n и не временный. n является членом лямбда-функции-объекта, который создается с помощью лямбда-выражения. Ожидание по умолчанию заключается в том, что вызов вашей лямбды не изменяет ее состояние, поэтому это const, чтобы предотвратить вас от случайного изменения n.

теперь есть предложение, чтобы облегчить необходимость mutable в лямбда объявления: n3424

чтобы расширить ответ щенка, лямбда-функции предназначены для чистые функции. Это означает, что каждый вызов, заданный уникальным входным набором, всегда возвращает один и тот же выход. Давайте определим вход как набор всех аргументов плюс все захваченные переменные при вызове лямбды.

в чистых функциях выход зависит исключительно от входа, а не от некоторого внутреннего состояния. Поэтому любая лямбда-функция, если она чистая, не нуждается в изменении своего состояния и поэтому является неизменный.

когда лямбда захватывает по ссылке, запись на захваченных переменных является напряжением для концепции чистой функции, потому что все, что должна делать чистая функция, это возвращать вывод, хотя лямбда, безусловно, не мутирует, потому что запись происходит с внешними переменными. Даже в этом случае правильное использование подразумевает, что если лямбда вызывается с тем же входом снова, выход будет одинаковым каждый раз, несмотря на эти побочные эффекты на переменные by-ref. Такой побочный эффект это просто способы вернуть некоторые дополнительные входные данные (например, обновить счетчик) и могут быть переформулированы в чистую функцию, например, возвращая кортеж вместо одного значения.

когда вы используете лямбда в качестве параметра функции, ответ выделяется!

Comments

    Ничего не найдено.