Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 - [97]

Шрифт
Интервал

>void Widget::addFilter() const {

> filters.emplace_back(

>  [=](int value) { return value % divisor == 0; }

> );

>}

>this->divisor захватывается указатель >this объекта >Widget, а не >divisor. Компиляторы рассматривают этот код так, как будто он написан следующим образом:

>void Widget::addFilter() const {

> auto currentObjectPtr = this;

> filters.emplace_back(

>  [currentObjectPtr](int value)

>  { return value % curentObjectPtr->divisor == 0;}

> );

>}

Понимание этого равносильно пониманию того, что жизнеспособность замыканий, вытекающих из этого лямбда-выражения, связана со временем жизни объекта >Widget, копии указателя >this которого в них содержатся. В частности, рассмотрим код, который в соответствии с главой 4 использует только интеллектуальные указатели:

>using FilterContainer =       // Как и ранее

>std::vector>;


>FilterContainer filters;      // Как и ранее


>void doSomeWork() {

> auto pw =                    // Создание Widget;

>  std::make_unique(); // std::make_unique см. в

>                              // разделе 4.4

> pw->addFilter();             // Добавление фильтра

>                              // с Widget::divisor

>} // Уничтожение Widget; filters хранит висячий указатель!

Когда выполняется вызов >doSomeWork, создается фильтр, зависящий от объекта >Widget, созданного >std::make_unique, т.e. фильтр, который содержит копию указателя на этот >Widget, — указатель >this объекта >Widget. Этот фильтр добавляется в >filters, но по завершении работы >doSomeWork объект >Widget уничтожается, так как >std::unique_ptr управляет его временем жизни (см. раздел 4.1). С этого момента >filters содержит элемент с висячим указателем.

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

>void Widget::addFilter() const {

> auto divisorCopy = divisor;           // Копирование

> filters.emplace_back(                 // члена-данных

>  [divisorCopy](int value)             // Захват копии

>  { return value % divisorCopy == 0; } // Ее использование

> );

>);

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

>void Widget::addFilter() const {

> auto divisorCopy = divisor;           // Копирование

> filters.emplace_back(                 // члена-данных

>  [=](int value)                       // Захват копии

>  { return value % divisorCopy == 0; } // Ее использование

> );

>}

но зачем искушать судьбу? Режим захвата по умолчанию делает возможным случайный захват >this, когда вы думаете, в первую очередь, о захвате >divisor.

В С++14 имеется лучший способ захвата члена-данных, заключающийся в использовании обобщенного захвата лямбда-выражения (см. раздел 6.2):

>void Widget::addFilter() const {

> filters.emplace_back(             // С++14:

>  [divisor = divisor](int value)   // Копирование divisor

>                                   // в замыкание

>  { return value % divisor == 0; } // Использование копии

> );

>}

Однако такого понятия, как режим захвата по умолчанию для обобщенного захвата лямбда-выражения, не существует, так что даже в С++14 остается актуальным совет данного раздела — избегать режимов захвата по умолчанию.

Дополнительным недостатком режимов захвата по умолчанию является то, что они могут предполагать самодостаточность соответствующих замыканий и их изолированность от изменений внешних данных. В общем случае это не так, поскольку лямбда-выражения могут зависеть не только от локальных переменных и параметров (которые могут быть захвачены), но и от объектов со статическим временем хранения. Такие объекты определены в глобальной области видимости или области видимости пространства имен или объявлены как >static внутри классов, функций или файлов. Эти объекты могут использоваться внутри лямбда-выражений, но не могут быть захвачены. Тем не менее спецификация режима захвата по умолчанию может создать именно такое впечатление. Рассмотрим преобразованную версию функции >addDivisorFilter, с которой мы встречались ранее:

>void addDivisorFilter() {

> static auto calc1

>  = computeSomeValue1() ;          // Статический

> static auto calc2

>  = computeSomeValue2();           // Статический


> static auto divisor =             // Статический

>  computeDivisor(calc1, calc2);


> filters.emplace_back(

>  [=](int value)                   // Ничего не захватывает

>  { return value % divisor == 0; } // Ссылка на статическую

> );                                // переменную


> ++divisor;                        // Изменение divisor

>}

Случайный читатель этого кода может быть прощен за то, что, видя >[=] , может подумать “Отлично, лямбда-выражение делает копию всех объектов, которые использует, и поэтому оно является самодостаточным”. Но это не так. Это лямбда-выражение не использует никакие нестатические локальные переменные, поэтому ничего не захватывается. Вместо этого код лямбда-выражения обращается к статической переменной >divisor. Когда в конце каждого вызова >addDivisorFilter выполняется увеличение >divisor, все лямбда-выражения, которые были добавлены в >filters с помощью данной функции, будут демонстрировать новое поведение (соответствующее новому значению


Еще от автора Скотт Мейерс
Эффективное использование STL

В этой книге известный автор Скотт Мейерс раскрывает секреты настоящих мастеров, позволяющие добиться максимальной эффективности при работе с библиотекой STL.Во многих книгах описываются возможности STL, но только в этой рассказано о том, как работать с этой библиотекой. Каждый из 50 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.


Как функции, не являющиеся методами, улучшают инкапсуляцию

Когда приходится инкапсулировать, то иногда лучше меньше, чем большеЯ начну со следующего утверждения: Если вы пишете функцию, которая может быть выполнена или как метод класса, или быть внешней по отношению к классу, Вы должны предпочесть ее реализацию без использования метода. Такое решение увеличивает инкапсуляцию класса. Когда Вы думаете об использовании инкапсуляции, Вы должны думать том, чтобы не использовать методы.Удивлены? Читайте дальше.


Рекомендуем почитать
Pro Git

Разработчику часто требуется много сторонних инструментов, чтобы создавать и поддерживать проект. Система Git — один из таких инструментов и используется для контроля промежуточных версий вашего приложения, позволяя вам исправлять ошибки, откатывать к старой версии, разрабатывать проект в команде и сливать его потом. В книге вы узнаете об основах работы с Git: установка, ключевые команды, gitHub и многое другое.В книге рассматриваются следующие темы:основы Git;ветвление в Git;Git на сервере;распределённый Git;GitHub;инструменты Git;настройка Git;Git и другие системы контроля версий.


Java 7

Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.


MFC и OpenGL

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


Симуляция частичной специализации

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


Обработка событий в С++

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


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.