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

стр.

обычно выполняются медленнее, чем вызовы посредством объекта, объявленного как >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 v;

>…

>unsigned sz = v.size();

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

Применение >auto гарантирует, что вам не придется этим заниматься:

>auto sz = v.size(); // Тип sz – std::vector::size_type

Все еще не уверены в разумности применения >auto? Тогда рассмотрите следующий код.

>std::unordered_map m;

>…

>for (const std::pair& p: m) {

> … // Что-то делаем с p

>}

Выглядит вполне разумно… но есть одна проблема. Вы ее не видите?

Чтобы разобраться, что здесь не так, надо вспомнить, что часть >std::unordered_map, содержащая ключ, является константной, так что тип >std::pair в хеш-таблице (которой является >std::unordered_map) вовсе не >std::pair, а >std::pair. Но переменная >p в приведенном выше цикле объявлена иначе. В результате компилятор будет искать способ преобразовать объекты >std::pair (хранящиеся в хеш-таблице) в объекты >std::pair (объявленный тип >p). Этот способ — создание временного объекта типа, требуемого >p, чтобы скопировать в него каждом объект из >m с последующим связыванием ссылки >p с этим временным объектом. В конце каждой итерации цикла временный объект уничтожается. Если этот цикл написан вами, вы, вероятно, будете удивлены его поведением, поскольку почти наверняка планировали просто связывать ссылку >p с каждым элементом в >m.

Такое непреднамеренное несоответствие легко лечится с помощью >auto:

>for (const auto& p : m) {

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

>}

Это не просто более эффективно — это еще и менее многословно. Кроме того, этот код имеет очень привлекательную особенность — если вы возьмете адрес >p, то можете быть уверены, что получите указатель на элемент в >m. В коде, не использующем >auto, вы получите указатель на временный объект — объект, который будет уничтожен в конце итерации цикла.

Два последних примера — запись >unsigned там, где вы должны были написать >std::vector::size_type, и запись >std::pair там, где вы должны были написать >std::pair, — демонстрируют, как явное указание типов может привести к неявному их преобразованию, которое вы не хотели и не ждали. Если вы используете в качестве типа целевой переменной