Читать «Эффективный и современный С++. 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-разрядную?