Эффективный и современный С++. 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
>…
>unsigned sz = v.size();
Официальный возвращаемый тип >v.size()
— >std::vector
, но об этом знает не так уж много разработчиков. >std::vector
определен как беззнаковый целочисленный тип, так что огромное количество программистов считают, что >unsigned
вполне достаточно, и пишут исходные тексты, подобные показанному выше. Это может иметь некоторые интересные последствия. В 32-разрядной Windows, например, и >unsigned
, и >std::vector
имеют один и тот же размер, но в 64-разрядной Windows >unsigned
содержит 32 бита, а >std::vector
— 64 бита. Это означает, что код, который работал в 32-разрядной Windows, может вести себя некорректно в 64-разрядной Windows. И кому хочется тратить время на подобные вопросы при переносе приложения с 32-разрядной операционной системы на 64-разрядную?
Применение >auto
гарантирует, что вам не придется этим заниматься:
>auto sz = v.size(); // Тип sz – std::vector
Все еще не уверены в разумности применения >auto
? Тогда рассмотрите следующий код.
>std::unordered_map
>…
>for (const std::pair
> … // Что-то делаем с 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
, и запись >std::pair
там, где вы должны были написать >std::pair
, — демонстрируют, как явное указание типов может привести к неявному их преобразованию, которое вы не хотели и не ждали. Если вы используете в качестве типа целевой переменной