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

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

(generalized lambda capture).

Но что если один или несколько используемых вами компиляторов не поддерживают инициализирующий захват С++14? Как выполнить перемещающий захват в языке, в котором нет поддержки перемещающего захвата?

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

>class IsValAndArch (

>public:

> using DataType = std::unique_ptr;


> explicit IsValAndArch(DataType&& ptr) // Применение std::move

> : pw(std::move(ptr)) {}               // описано в разделе 5.3


> bool operator()() const

> { return pw->isValidated() && pw->isArchived(); }

>private:

> DataType pw;

>};


>auto func = IsValAndArch(std::make_unique());

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

Если вы хотите придерживаться лямбда-выражений (с учетом их удобства это, вероятно, так и есть), то перемещающий захват можно эмулировать в C++11 с помощью

1. перемещения захватываемого объекта в функциональный объект с помощью>std::bind и

2. передачи лямбда-выражению ссылки на захватываемый объект.

Если вы знакомы с >std::bind, код достаточно прост. Если нет, вам придется немного привыкнуть к нему, но игра стоит свеч.

Предположим, вы хотите создать локальный >std::vector, разместить в нем соответствующее множество значений, а затем переместить его в замыкание. В С++ 14 это просто:

>std::vector data;            // Объект, перемещаемый

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

>…                                    // Наполнение данными

>auto func = [data = std::move(data)] // Инициализирующий захват

>            { /* Использование данных */ };

Я выделил ключевые части этого кода: тип объекта, который вы хотите перемещать (>std::vector), имя этого объекта (>data) и выражение инициализации для инициализирующего захвата (>std::move(data)). Далее следует эквивалент этого кода на С++11, в котором я выделил те же ключевые части:

>std::vector data;             // Как и ранее

>…                                     // Как и ранее

>auto func =

> std::bind(                           // Эмуляция в С++ 11

>  [](const std::vector& data) // инициализирующего

>  { /* Использование данных */ },     // захвата

>  std::move(data)

> );

Подобно лямбда-выражениям, >std::bind создает функциональные объекты. Я называю функциональные объекты, возвращаемые >std::bind, bind-объектами. Первый аргумент >std::bind — вызываемый объект. Последующие аргументы представляют передаваемые этому объекту значения.

Bind-объект содержит копии всех аргументов, переданных >std::bind. Для каждого lvalue-аргумента соответствующий объект в bind-объекте создается копированием. Для каждого rvalue он создается перемещением. В данном примере второй аргумент представляет собой rvalue (как результат применения >std::move; см. раздел 5.1), так что >data перемещается в bind-объект. Это перемещающее создание является сутью эмуляции перемещающего захвата, поскольку перемещение rvalue в bind-объект и есть обходной путь для перемещения rvalue в замыкание С++11.

Когда bind-объект “вызывается” (т.e. выполняется его оператор вызова функции), сохраненные им аргументы, первоначально переданные в >std::bind, передаются в вызываемый объект. В данном примере это означает, что когда вызывается bind-объект >func, лямбда-выражению, переданному в >std::bind, в качестве аргумента передается созданная в >func перемещением копия >data.

Это лямбда-выражение то же самое, что и использованное нами в C++14, за исключением добавленного параметра >data. Этот параметр представляет собой lvalue-ссылку на копию >data в bind-объекте. (Это не rvalue-ссылка, поскольку, хотя выражение, использованное для инициализации копии >data (“>std::move(data)”), является rvalue, сама по себе копия >data представляет собой lvalue.) Таким образом, применение >data внутри лямбда-выражения будет работать с копией >data внутри bind-объекта, созданной перемещением.

По умолчанию функция-член >operator() в классе замыкания, сгенерированном из лямбда-выражения, является >const. Это приводит к тому, что все члены-данные в замыкании в теле лямбда-выражения являются константными. Однако созданная перемещением копия >data внутри bind-объекта не является константной, так что, чтобы предотвратить модификацию этой копии >data внутри лямбда-выражения, параметр лямбда-выражения объявляется как указатель на >const. Если лямбда-выражение было объявлено как >mutable, >operator() в его классе замыкания не будет объявлен как >const, так что целесообразно опустить >const в объявлении параметра лямбда-выражения:

>auto func =

> std::bind(                             // Эмуляция в C++11

>  [](std::vector& data) mutable // инициализирующего

>  { /* uses of data */ },               // захвата для лямбда-


Еще от автора Скотт Мейерс
Эффективное использование 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 так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.