С. Мейерс - Эффективный и современный C++ (1114942), страница 22
Текст из файла (страница 22)
Но давайте подумаем логически. Этот шаблон cbegin принимает3 .7. Предпочитайте итераторы const_iterator итераторам iterator97аргументы любого типа, представляющего контейнерообразную структуру данных С ,и обращается к этому аргументу через ссылку на константный объект contaiпer. ЕслиС - обычный тип контейнера (например, s t d : : vector<iпt> ) , то coпta iпer будет ссылкой на сопst-версию этого контейнера (например, coпst std : : vector< i n t > & ) . Вызовфункции beg iп, не являющейся членом (и предоставленной С++ 1 1 ), для константногоконтейнера дает константный итератор coпst_ iterat or, и именно этот итератор и возвращает наш шаблон.
Преимущества такой реализации в том, что она работает дажедля контейнеров, которые предоставляют функцию-член beg iп (которую функция С++ l lbegi п, не являющаяся членом, вызывает для контейнеров), но не имеют функции-членаcbegiп. Таким образом, вы можете использовать эту свободную функцию cbegiп с контейнерами, поддерживающими единственную функцию-член beg iп.Этот шаблон работает и в случае, когда С представляет собой встроенный массив.При этом coпta i пer становится ссылкой на константный массив. C++ l l предоставляет для массивов специализированную версию функции beg iп, не являющейся членом,которая возвращает указатель на первый элемент массива. Элементы константного массива являются константами, так что указатель, который возвращает свободная функцияbeg i п, возвращает для константного массива указатель на константу, который фактически и является coпst_ i terator для массива.
(Для понимания, как специализироватьшаблон для встроенных массивов, обратитесь к обсуждению в разделе l . l вывода типовв шаблонах, которые получают в качестве параметров ссылки на массивы.)Но вернемся к основам. Суть этого раздела заключается в том, чтобы призвать вас использовать coпst _ i terator везде, где возможно. Фундаментальный мотив для этого применение const всегда, когда это имеет смысл - существовал и до С++ 1 1 , но в С++98этот принцип при работе с итераторами был непрактичным.
В С++ 1 1 это сугубо практический совет, а в С++ 14 к тому же "доведены до ума" некоторые мелочи, остававшиесянезавершенными в С++ 1 1 .Сл едует зап омнить•Предпочитайте использовать coпst _i terator вместо i terator там, где это можно.•В максимально обобщенном коде предпочтительно использовать версии функцийbegin, end, rbegiп и прочих, не являющиеся членами.3 .8. Есп и функции не rенерируют искпючений,объявпяйте их как noexceptВ С++98 спецификации исключений были довольно темпераментными созданиями.От вас требовалось собрать информацию обо всех типах исключений, которые моглагенерировать функция, так что при изменении реализации функции могла потребоватьизменения и спецификация исключений.
Изменение спецификации исключения моглонарушить клиентский код, так как вызывающий код мог зависеть от исходной спецификации исключений. Компиляторы обычно не предлагали никакой помощи в поддержании согласованности между реализациями функций, спецификациями исключений98Глава З . Переход к современному С++и клиентским кодом. Большинство программистов в конечном итоге сочло, что спецификации исключений в С++98 не стоят затрачиваемых на н их усилий и ими лучше не пользоваться вовсе.
Во время работы над С++ 1 1 было достигнуто согласие, что действительно важная информация о поведении функций в смысле исключений - это информация,может ли вообще такое исключение быть сгенерировано. Либо функция может генерировать исключение, либо гарантируется, что это невозможно. Именно эта дихотомия лежитв основе спецификаций исключений С++ 1 1, которые, по сути, заменили спецификацииисключений С++98. (Спецификации исключений в стиле С++98 остаются корректными,но не рекомендуются к употреблению.) В С++ 1 1 безусловный модификатор noexceptприменяется к функциям, которые гарантированно не могут генерировать исключения.Должна ли функция быть объявлена таким образом - вопрос проектирования интерфейса. Поведение генерирующих исключения функций представляет большой интересдля клиентов.
Вызывающий код может запросить статус noexcept функции, и результатэтого запроса может повлиять на безопасность в смысле исключений или эффективностьвызывающего кода. Таким образом, является ли функция объявленной как noexcept,представляет собой столь же важную часть информации, как и является ли функция-членобъявленной как const. Отсутствие объявления функции как noexcept, когда вы точнознаете, что она не в состоянии генерировать исключения, - не более чем просто плохаяспецификация интерфейса.Но есть и дополнительный стимул для применения noexcept к функциям, которые негенерируют исключений: это позволяет компиляторам генерировать лучший объектныйкод.
Чтобы понять, почему это так, рассмотрим разницу между способами, которымиС++98 и С++ 1 1 сообщают о том, что функция не может генерировать исключения. Пустьимеется функция f, которая обещает вызывающему коду, что тот никогда не получит исключения.
Вот как выглядят два способа это выразить:int f ( int х) throw ( ) ; // f не генерирует исключений : С++98int f ( int х) noexcept; / / f не генерирует исключений : C++ l lЕсли в о время выполнения некоторое исключение покинет f , тем самым будет нарушена спецификация исключений f. При спецификации исключений С++98 стек вызововсворачивается' до вызывающего f кода, и после некоторых действий, не имеющих значения для данного рассмотрения, выполнение программы прекращается.
При спецификации исключений С++ 1 1 поведение времени выполнения несколько иное: стек только,возможно, сворачивается перед завершением выполнения программы.5Обычно в русскоязычной литературе для термина stack ипwiпdiпg используется перевод "разворачивание стека". Однако это не совсем верный перевод. Вот что в переписке с редактором книгипишет по этому поводу профессор университета И ннополис Е. Зуев: "Самый частый вариантперевода - «раскрутка стека» - не просто затемняет существо дела, но просто-таки противоположен ему.
При срабатывании исключения начинается процесс поиска в стеке секции ("кадрастека", stack fraтe), для которой задан перехват случившегося исключения. В тексте исходнойпрограммы такой секции соответствует trу-блок с саtсh-обработчиком, в котором задано имяслучившегося исключения. И в процессе этого поиска все секции стека, для которых такой перехват не задан, из стека удаляются. Говорят еще, что производится поиск секции по всей динамической цепочке вь1зовов.
Тем самым стек в целом сокращается, сворачивается. Таким образом,самый адекватный вариант перевода - сворачивание стека': Поэтому принято решение переводить stack ипwiпdiпg как сворачивание стека. - Примеч. ред.3.8. Есл и функции не генерируют исключений, обьявляйте их как noexcept99Разница между сворачиванием стека и возможным сворачиванием оказывает на удивление большое влияние на генерацию кода.
В случае функции, объявленной как noexcept,оптимизаторам не надо ни поддерживать стек в сворачиваемом состоянии, ни гарантировать, что объекты в такой функции будут уничтожены в порядке, обратном созданию,если вдруг такую функцию покинет исключение. Функции со спецификацией throw ( ) неимеют такой гибкости оптимизации, как и функции без спецификаций вообще. Ситуацию можно резюмировать следующим образом:Ret Type fun c t i on (pa rams) noexcept; / / Наиболее оптимизируемаRet Type fun c t i on (params) throw ( ) ;/ / Менее оптимизируемаR e t Type fun c t i on (pa rams ) ;!!Менее оптимизируемаЭтого одного достаточно для того, чтобы объявлять функции, о которых точно известно, что они не генерируют исключений, как noexcept .Для некоторых функций все оказывается еще более интересным.
Выдающимся примером являются операции перемещения. Предположим, что у вас имеется код С++98,использующий std : : vector<Widget>. Объекты типа W i dget время от времени добавляются в s t d : : vector с помощью функции push_back:s td : : vector<Widget>vw;Widget w;11vw . push_bac k ( w ) ;Работас w/ / Добавлениеw к vwПредположим, что этот код отлично работает, и нет никакой необходимости изменять его для С++ 1 1 . Однако вы хотите воспользоваться тем фактом, что семантика перемещения С++ 1 1 может улучшить производительность старого кода при участии типов,допускающих перемещающие операции.
Вы уверены, что класс Widget такие операцииимеет - либо потому, что вы написали их самостоятельно, либо потому, что вы убедились в осуществлении условий для их автоматической генерации (см. раздел 3. 1 1 ).Когда в std : : vector добавляется новый элемент, может оказаться, что в std : : vectorдля него не хватает места, т.е. что размер std : : vector равен его емкости. Когда такоеслучается, std : : vector выделяет новый, больший по размеру блок памяти для хранения своих элементов и переносит элементы из старого блока в новый.