http://programmingmindstream.blogspot.ru/2014/11/delphi_21.html?showComment=1416695404969#c6071212647984907418
Есть "поучительная история" про "лямбды".
Про C++.
https://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%BC%D1%8B%D0%BA%D0%B0%D0%BD%D0%B8%D0%B5_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
https://ru.wikipedia.org/wiki/C%2B%2B11#.D0.9B.D1.8F.D0.BC.D0.B1.D0.B4.D0.B0-.D1.84.D1.83.D0.BD.D0.BA.D1.86.D0.B8.D0.B8_.D0.B8_.D0.B2.D1.8B.D1.80.D0.B0.D0.B6.D0.B5.D0.BD.D0.B8.D1.8F
Процитирую:
"
И ещё:
"std::vector<int> someList;
Много букв процитировал.
А сам напишу коротко:
Пусть есть класс:
- обратим внимание - тут НЕТ нормального копирующего конструктора и оператора присваивания.
Теперь пусть есть код:
- казалось бы - "ничего такого" - мы тут не сделали.
Просто один раз дёрнули подитеративную функцию.
Однако мы получим AV.
Почему?
А потому, что по-умолчанию лямбды "захватывают" все внешние переменные - по значению.
Я ведь - не зря всё это выше цитировал.
Что тут происходит?
Обратим внимание на строчку:
- казалось бы тут "ничего такого".
Однако - это не так.
Если эту строчку закомментировать, то AV - пропадёт.
В чём же дело?
А вот в чём.
Мы ведь зовём x.getString() внутри анонимного метода.
И что происходит?
А то, что за x.getString() - скрывается copy_of_x.getString(), которая получается при создании анонимного метода.
А как получается?
По значению. Читаем выше.
И как по значению? Через вызов копирующего конструктора, который у нас - умолчательный.
Который просто копирует поля класса.
Соответственно поле m_String просто копируется при получении copy_of_x из x.
А освобождается эта строка ДВА раза - для x и для copy_of_x.
Откуда AV - теперь понятно?
Теперь что же делать?
Самый простой вариант это:
- что мы тут сделали?
Мы написали - [&x] вместо [].
Этим мы сказали, что x явно передаётся по ссылке, а не по значению.
И тогда - копия сниматься - не будет.
Ну или можно определить копирующий конструктор:
-- тогда при снятии копии будет вызываться "правильный" копирующий конструктор, который будет снимать копию строки, а не просто копировать указатель.
И тогда будут работать оба варианта вызова - и [] и [&x].
Теперь как сделать так, чтобы вызов по значению с [] был невозможен?
Ну пусть природа нашего класса такова, что его нельзя копировать.
Как это сделать?
Ну в "обычном C++" можно объявить приватный пустой копирующий конструктор:
- и тогда на "факт копирования" ругнётся компилятор. Скажет "неопределённый копирующий конструктор".
В C++11 можно сделать более "красиво" - удалить копирующий конструктор по-умолчанию:
- результат будет тем же. На "факт копирования" ругнётся компилятор.
Ну вот собственно пока и всё.
Ни на что не претендую.
Нет. Ещё не всё...
Про Objective-C - забыл.
Там можно написать так:
И внутри неё используется x.getString(), которая тоже copy_of_x.getString().
И этой "лямбде" нет средств указать, что передаём по значению.
Ну кроме модификатора __block. Но там тоже - "есть нюансы".
И посему - для такого кода можно только или запретить копирующий конструктор, или определить его правильно.
Вот - теперь всё.
Есть "поучительная история" про "лямбды".
Про C++.
https://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%BC%D1%8B%D0%BA%D0%B0%D0%BD%D0%B8%D0%B5_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
https://ru.wikipedia.org/wiki/C%2B%2B11#.D0.9B.D1.8F.D0.BC.D0.B1.D0.B4.D0.B0-.D1.84.D1.83.D0.BD.D0.BA.D1.86.D0.B8.D0.B8_.D0.B8_.D0.B2.D1.8B.D1.80.D0.B0.D0.B6.D0.B5.D0.BD.D0.B8.D1.8F
Процитирую:
"
Лямбда-функции и выражения
В стандартном C++, например, при использовании алгоритмов стандартной библиотеки C++ sort и find, часто возникает потребность в определении функций-предикатов рядом с местом, где осуществляется вызов этого алгоритма. В языке существует только один механизм для этого: возможность определить класс функтора (передача экземпляра класса, определенного внутри функции, в алгоритмы запрещена (Meyers, Effective STL)). Зачастую данный способ является слишком избыточным и многословным и лишь затрудняет чтение кода. Кроме того, стандартные правила C++ для классов, определённых в функциях, не позволяют использовать их в шаблонах и таким образом делают их применение невозможным.
Очевидным решением проблемы явилось разрешение определения лямбда-выражений и лямбда-функций в C++11. Лямбда-функция определяется следующим образом:
Тип возвращаемого значения этой безымянной функции вычисляется как decltype(x+y). Тип возвращаемого значения может быть опущен только в том случае, если лямбда-функция представлена в форме
return expression
. Это ограничивает размер лямбда-функции до одного выражения.
Тип возвращаемого значения может быть указан явно, например:
В этом примере создаётся временная переменная z для хранения промежуточного значения. Как и в нормальных функциях, это промежуточное значение не сохраняется между вызовами.
Тип возвращаемого значения может быть полностью опущен, если функция не возвращает значения (то есть тип возвращаемого значения — void)
Также возможно использование ссылок на переменные, определённые в той же области видимости, что и лямбда-функция. Набор таких переменных обычно называют замыканием. Замыкания определяются и используются следующим образом:"И ещё:
"std::vector<int> someList;
Это отобразит сумму всех элементов в списке. Переменная total хранится как часть замыкания лямбда-функции. Так как она ссылается на стековую переменную total, она может менять её значение.
Переменные замыкания для локальных переменных могут быть также определены без использования символа ссылки &, что означает, что функция будет копировать значение. Это вынуждает пользователя заявлять о намерении сослаться на локальную переменную или скопировать её."Много букв процитировал.
А сам напишу коротко:
Пусть есть класс:
class A { private: char * m_String; public A (char * aString) { m_String = strnew(aString); // - получили копию строки } ~A() { strdispose(m_String); // - освободили строку } const char * getString() { return m_String; } };
- обратим внимание - тут НЕТ нормального копирующего конструктора и оператора присваивания.
Теперь пусть есть код:
A x ("Hello world"); char * y; std::vector<int> someList = {1}; // - просто чтобы вектор был не пуст, хотя это и не так важно std::for_each(someList.begin(), someList.end(), [](int anItem) { y := x.getString(); // - типа просто получили ссылку на строку и ничего с ней не делаем });
- казалось бы - "ничего такого" - мы тут не сделали.
Просто один раз дёрнули подитеративную функцию.
Однако мы получим AV.
Почему?
А потому, что по-умолчанию лямбды "захватывают" все внешние переменные - по значению.
Я ведь - не зря всё это выше цитировал.
Что тут происходит?
Обратим внимание на строчку:
y := x.getString();
- казалось бы тут "ничего такого".
Однако - это не так.
Если эту строчку закомментировать, то AV - пропадёт.
В чём же дело?
А вот в чём.
Мы ведь зовём x.getString() внутри анонимного метода.
И что происходит?
А то, что за x.getString() - скрывается copy_of_x.getString(), которая получается при создании анонимного метода.
А как получается?
По значению. Читаем выше.
И как по значению? Через вызов копирующего конструктора, который у нас - умолчательный.
Который просто копирует поля класса.
Соответственно поле m_String просто копируется при получении copy_of_x из x.
А освобождается эта строка ДВА раза - для x и для copy_of_x.
Откуда AV - теперь понятно?
Теперь что же делать?
Самый простой вариант это:
A x ("Hello world"); char * y; std::vector<int> someList = {1}; // - просто чтобы вектор был не пуст, хотя это и не так важно std::for_each(someList.begin(), someList.end(), [&x](int anItem) { y := x.getString(); // - типа просто получили ссылку на строку и ничего с ней не делаем });
- что мы тут сделали?
Мы написали - [&x] вместо [].
Этим мы сказали, что x явно передаётся по ссылке, а не по значению.
И тогда - копия сниматься - не будет.
Ну или можно определить копирующий конструктор:
class A { private: char * m_String; public A (char * aString) { m_String = strnew(aString); // - получили копию строки } A (const A & anOther) { m_String = strnew(anOther.m_String); // - получили копию строки } ~A() { strdispose(m_String); // - освободили строку } const char * getString() { return m_String; } };
-- тогда при снятии копии будет вызываться "правильный" копирующий конструктор, который будет снимать копию строки, а не просто копировать указатель.
И тогда будут работать оба варианта вызова - и [] и [&x].
Теперь как сделать так, чтобы вызов по значению с [] был невозможен?
Ну пусть природа нашего класса такова, что его нельзя копировать.
Как это сделать?
Ну в "обычном C++" можно объявить приватный пустой копирующий конструктор:
class A { private: char * m_String; public A (char * aString) { m_String = strnew(aString); // - получили копию строки } private: A (const A & anOther); public: ~A() { strdispose(m_String); // - освободили строку } const char * getString() { return m_String; } };
- и тогда на "факт копирования" ругнётся компилятор. Скажет "неопределённый копирующий конструктор".
В C++11 можно сделать более "красиво" - удалить копирующий конструктор по-умолчанию:
class A { private: char * m_String; public A (char * aString) { m_String = strnew(aString); // - получили копию строки } A (const A & anOther) = delete; ~A() { strdispose(m_String); // - освободили строку } const char * getString() { return m_String; } };
- результат будет тем же. На "факт копирования" ругнётся компилятор.
Ну вот собственно пока и всё.
Ни на что не претендую.
Нет. Ещё не всё...
Про Objective-C - забыл.
Там можно написать так:
A x ("Hello world"); char * y; [m_HiddenTextIndex enumerateKeysAndObjectsUsingBlock:^(id docId, id data, BOOL *) { y := x.getString(); // - типа просто получили ссылку на строку и ничего с ней не делаем }];-- ^(id docId, id data, BOOL *) - это тоже анонимная функция.
И внутри неё используется x.getString(), которая тоже copy_of_x.getString().
И этой "лямбде" нет средств указать, что передаём по значению.
Ну кроме модификатора __block. Но там тоже - "есть нюансы".
И посему - для такого кода можно только или запретить копирующий конструктор, или определить его правильно.
Вот - теперь всё.
Комментариев нет:
Отправить комментарий