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

Скотт Мейерс

std::function<bool(const std::unique_ptr<Widget>&,

                   const std::unique_ptr<Widget>&)> func;

Поскольку лямбда-выражения дают вызываемые объекты, замыкания могут храниться в объектах std::function. Это означает, что можно объявить С++11-версию derefUPLess без применения auto следующим образом:

std::function<bool(const std::unique_ptr<Widget>&,

                   const std::unique_ptr<Widget>&)>

 derefUPLess = [](const std::unique_ptr<Widget>& p1,

                  const std::unique_ptr<Widget>& p2)

                  { return *p1 < *p2; };

Важно понимать, что, даже если оставить в стороне синтаксическую многословность и необходимость повторения типов параметров, использование std::function — не то же самое, что использование auto. Переменная, объявленная с использованием auto и хранящая замыкание, имеет тот же тип, что и замыкание, и как таковая использует только то количество памяти, которое требуется замыканию. Тип переменной, объявленной как std::function и хранящей замыкание, представляет собой конкретизацию шаблона std::function, которая имеет фиксированный размер для каждой заданной сигнатуры. Этот размер может быть не адекватным для замыкания, которое требуется хранить, и в этом случае конструктор std::function будет выделять для хранения замыкания динамическую память. В результате объект std::function использует больше памяти, чем объект, объявленный с помощью auto. Кроме того, из-за деталей реализации это ограничивает возможности встраивания и приводит к косвенным вызовам функции, так что вызовы замыкания через объект std::function обычно выполняются медленнее, чем вызовы посредством объекта, объявленного как auto. Другими словами, подход с использованием std::function в общем случае более громоздкий, требующий больше памяти и более медленный, чем подход с помощью auto, и к тому же может приводить к генерации исключений, связанных с нехваткой памяти. Ну и, как вы уже видели в примерах выше, написать “auto” — гораздо проще, чем указывать тип для инстанцирования std::function. В соревновании между auto и std::function для хранения замыкания побеждает auto. (Подобные аргументы можно привести и в пользу предпочтения auto перед std::function для хранения результатов вызовов std::bind, но все равно в разделе 6.4 я делаю все, чтобы убедить вас использовать вместо std::bind лямбда-выражения...)

Преимущества auto выходят за рамки избегания неинициализированных переменных, длинных объявлений переменных и возможности непосредственного хранения замыкания. Кроме того, имеется возможность избежать того, что я называю проблемой “сокращений типа” (type shortcuts). Вот кое-что, что вы, вероятно, уже видели, а возможно, даже писали:

std::vector<int> v;

unsigned sz = v.size();

Официальный возвращаемый тип v.size()std::vector<int>::size_type, но об этом знает не так уж много разработчиков. std::vector<int>::size_type определен как беззнаковый целочисленный тип, так что огромное количество программистов считают, что unsigned вполне достаточно, и пишут исходные тексты, подобные показанному выше. Это может иметь некоторые интересные последствия. В 32-разрядной Windows, например, и unsigned, и std::vector<int>::size_type имеют один и тот же размер, но в 64-разрядной Windows unsigned содержит 32 бита, а std::vector<int>::size_type — 64 бита. Это означает, что код, который работал в 32-разрядной Windows, может вести себя некорректно в 64-разрядной Windows. И кому хочется тратить время на подобные вопросы при переносе приложения с 32-разрядной операционной системы на 64-разрядную?