Читать «Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14» онлайн - страница 34
Скотт Мейерс
Следует запомнить
• Переменные, объявленные как auto
, должны быть инициализированы; в общем случае они невосприимчивы к несоответствиям типов, которые могут привести к проблемам переносимости или эффективности; могут облегчить процесс рефакторинга; и обычно требуют куда меньшего количества ударов по клавишам, чем переменные с явно указанными типами.
• Переменные, объявленные как auto
, могут быть подвержены неприятностям, описанным в разделах 1.2 и 2.2.
2.2. Если auto
выводит нежелательный тип, используйте явно типизированный инициализатор
В разделе 2.1 поясняется, что применение auto
для объявления переменных предоставляет ряд технических преимуществ по сравнению с явным указанием типов, но иногда вывод типа auto
идет налево там, где вы хотите направо. Предположим, например, что у меня есть функция, которая получает Widget
и возвращает std::vector<bool>
, где каждый bool
указывает, обладает ли Widget
определенным свойством:
std::vector<bool> features(const Widget& w);
Предположим далее, что пятый бит указывает наличие высокого приоритета у Widget
. Мы можем написать следующий код.
Widget w;
…
bool highPriority = features(w)[5]; // Имеет ли w высокий
// приоритет?
…
processWidget(w, highPriority); // Обработка w в соответ-
// ствии с приоритетом
В этом коде нет ничего неверного. Он корректно работает. Но если мы внесем кажущееся безобидным изменение и заменим явный тип highPriority
типом auto
auto highPriority = features(w)[5]; // Имеет ли w высокий
// приоритет?
то ситуация изменится. Код будет продолжать компилироваться, но его поведение больше не будет предсказуемым:
processWidget(w, highPriority); // Неопределенное поведение!
Как указано в комментарии, вызов processWidget
теперь имеет неопределенное поведение. Но почему? Ответ, скорее всего, вас удивит. В коде, использующем auto
, тип highPriority
больше не является bool
. Хотя концептуально std::vector<bool>
хранит значения bool
, operator[]
у std::vector<bool>
не возвращает ссылку на элемент контейнера (то, что std::vector::operator[]
возвращает для всех типов bool
). Вместо этого возвращается объект типа std::vector<bool>::reference
(класса, вложенного в std::vector<bool>
).
Тип std::vector<bool>::reference
существует потому, что std::vector<bool>
определен как хранящий значения bool
в упакованном виде, по одному биту на каждое значение. Это создает проблему для оператора operator[]
класса std::vector<bool>
, поскольку operator[]
класса std: :vector<T>
должен возвращать T&
, но С++ запрещает ссылаться на отдельные биты. Будучи не в состоянии вернуть bool&
, operator[]
класса std::vector<bool>
возвращает объект, который bool&
. Для успешной работы объекты std::vector<bool>::reference
должны быть применимы по сути во всех контекстах, где применим bool&
. Среди прочих возможностей std::vector<bool>::reference
обладает неявным преобразованием в bool
. (Не в bool&
, а именно в bool
. Пояснение всего набора методов, используемых std::vector<bool>::reference
для эмуляции поведения bool&
, завело бы нас слишком далеко, так что я просто замечу, что это неявное преобразование является только одним из камней в существенно большей мозаике.)