С. Мейерс - Эффективный и современный C++ (1114942), страница 24
Текст из файла (страница 24)
Такие деструкторы являются редкостью. Их нет в стандартной библиотеке, и если деструктор объекта, используемого стандартной библиотекой (например,как элемент контейнера или переданный алгоритму аргумент), генерирует исключение,поведение программы является неопределенным.Стоит отметить, что некоторые проектировщики библиотечных интерфейсов различают функции с широкими контрактами от функций с узкими контрактами. Функцияс широким контрактом не имеет предусловий. Такая функция может быть вызвана независимо от состояния программы и не накладывает никаких ограничений на аргументы,передаваемые ей вызывающим кодом".
Функции с широким контрактом никогда не демонстрируют неопределенного поведения." "Независимо от состояния программы" и "без ограничений" не узаконивает программы, поведениекоторых уже является неопределенным. Например, std : : vector: : size имеет широкий контракт, ноэто не означает, что данная функция донжна разумно себя вести при применении к произвопьномублоку памяти, приведенному к типу std: : vector. Результат приведения не определен, поэтому нетникаких гарантий, касающихся поведения программы, содержащей такое приведение.3.8.
Ес ли функции не генерируют исключений, объяв л яйте их как noexcept1 03Функции без широких контрактов имеют узкие контракты. Если предусловия для таких функций нарушены, их результаты являются неопределенными.Если вы пишете функцию с широким контрактом и знаете, что ее не покинут никакие исключения, легко следовать совету из данного раздела и объявить ее как noexcept.Для функций с узкими контрактами ситуация сложнее.
Предположим, например, что выпишете функцию f, принимающую параметр s t d : : s t r ing, и пусть естественная реализация f никогда не генерирует исключений. Это предполагает, что функция f должна бытьобъявлена как noexcept.Предположим теперь, что f имеет предусловие: длина ее строкового параметра s t d : : s t r i ng не должна превышать 32 символа. Если f вызывается со строкойs t d : : s t r i ng, длина которой больше 32 символов, поведение будет неопределенным,поскольку нарушение предусловия по определению приводит к неопределенному поведению. Функция f не обязана проверять это предусловие, так как функции могут считать свои предусловия выполненными. (За обеспечение выполнения таких предположений отвечает вызывающий код.) Тогда даже при наличии предусловия объявление f какnoexcept выглядит целесообразным:void f ( const std : : string& s) noexcept; / / Предусловие :11 s . length ( ) <= 32Но предположим, что разработчик f решил проверять нарушения предусловия.
Такаяпроверка не обязательна, но она и не запрещена; более того, проверка предусловия может быть полезной, например, в процессе системного тестирования. Отладка сгенерированного исключения в общем случае проще, чем попытки отследить причину неопределенного поведения. Но как следует сообщать о нарушении предусловий, чтобы проверки(или клиентский обработчик ошибок) могли его обнаружить� Простейший подход генерация исключения "предусловие нарушено': но если f объявлена как noexcept, этоможет быть невозможным; генерация исключения приведет к завершению программы.По этой причине разработчики библиотек, различающие широкие и узкие контракты,в общем случае резервируют noexcept для функций с широкими контрактами.В заключение позвольте мне остановиться на моих ранних наблюдениях, что обычнокомпиляторы не предлагают помощи в выявлении несоответствий между реализациямифункций и их спецификациями исключений.
Рассмотрим следующий совершенно корректный код:void setup ( ) ; / / Функции, определенные в другом местеvoid cleanup ( ) ;void doWor k ( ) noexcept11 Настройка для выполнения некоторой работы11 Выполнение работыcleanup ( ) ; 11 Очистка после вьиюлнения работыsetup ( ) ;104Гn ава 3. Переход к современному С++Здесь функция doWor k объявлена как noexcept, несмотря на то, что она вызываетфункции setup и cleanup без такого модификатора. Это кажется противоречием, но ведьможет быть так, что функции setup и cleanup документированы как не генерирующиеисключений, пусть при этом они и не объявлены как noexcept.
Для такого их объявлениямогут иметься некоторые веские причины. Например, они могут быть частью библиотеки,написанной на языке программирования С. (Даже у функций стандартной библиотеки С,перемещенных в пространство имен std, отсутствуют спецификации исключений; например, std : : st rlen не объявлена как noexcept.) Они также могут быть частью стандартнойбиблиотеки С++98, в которой решено не использовать спецификации исключений С++98и которая еще не пересмотрена на предмет использования возможностей С++ l l .Поскольку для функций, объявленных как noexcept, имеются законные основанияполагаться на код, не имеющий гарантии noexcept, С++ допускает существование такогокода, и компиляторы в общем случае не выдают при этом никаких предупреждений о нем.Следует запомнить•noexcept является частью интерфейса функции, а это означает, что вызывающийкод может зависеть от наличия данного модификатора.•Функции, объявленные как noexcept, предоставляют большие возможности оптимизации, чем функции без такой спецификации.•Спецификация noexcept имеет особое значение для операций перемещения, обмена, функций освобождения памяти и деструкторов.•Большинство функций нейтральны по отношению к исключениям, а не являютсяnоехсерt -функциями.3 .
9 . Испол ьзуйте, rде это возмож но, constexprЕсли бы присуждалась награда за наиболее запутанную новую возможность в С++ l l ,без сомнений, ее б ы получило новое ключевое слово con s t expr. При применениик объектам это, по сути, усиленная разновидность const, но при применении к функциям оно имеет совсем другой смысл. Имеет смысл разобраться в этой путанице, потомучто, когда ключевое слово constexpr соответствует тому, что вы, хотите выразить, выопределенно, захотите его использовать.Концептуально ключевое слово constexpr указывает значение, которое не просто является константой, но и известно во время компиляции.
Эта концепция - лишь частьвсей истории, поскольку при применении const expr к функциям появляется большенюансов, чем можно предположить. Чтобы не забегать вперед и не портить сюрприз,пока что я только скажу, что вы не можете ни считать, что результат соnstехрr-функциипредставляет константу, ни считать, что эти значения известны во время компиляции.Пожалуй, наиболее интригующим является то, что это возможности данного ключевогослова. Это хорошо, что const expr- функция не обязана давать константный результатили результат, известный во время компиляции!3.9. Используйте, rде это возможн о, constexpr105Но давайте начнем с объектов constexpr. Такие объекты являются, по сути, константными ( const ) и на самом деле обладают значениями, известными во время компиляции.(Технически их значения определяются во время трансляции, а трансляция состоит нетолько из компиляции, но и из компоновки.
Однако, если вы не пишете компиляторыили редакторы связей для С++, это не имеет для вас значения, так что можете беспечнопрограммировать так, как будто эти значения объектов constexpr определяются во время компиляции.)Значения, известные во время компиляции, являются привилегированными.
Они,например, могут быть размещены в памяти, предназначенной только для чтения, и этоможет представлять особое значение для разработчиков встроенных систем. Широкоприменяется то, что целочисленные значения, являющиеся константами и известные вовремя компиляции, могут использоваться в контекстах, где С++ требует целочисленноеконстантное вь1ражение. Такие контексты включают спецификации размеров массивов,целочисленные аргументы шаблонов (включая длину объектов s t d : : a r r a y) , значенияперечислителей, спецификаторы выравнивания и прочее. Если вы хотите использоватьдля этих вещей переменные, вы, определенно, захотите объявить их как constexpr, поскольку тогда компиляторы будут точно знать, что имеют дело со значением временикомпиляции:/ / Неконстантная переменнаяint s z ;constexpr auto arraySizel=s z ; / / Ошибка ! Значение sz/ / неизвестно при компиляции/ / Ошибка ! Т а ж е проблемаs td : : array<int, sz> data l ;constexpr auto arraySize21 0 ; 1 1 ОК, 1 0 представляет собой11 константу времени компиляции/ / ОК, arraySize2 представляетs td : : array<int,/ / собой constexprarraySize2> data2;Обратите внимание, что const не предоставляет таких же гарантий, что и constexpr,поскольку объекты const не обязаны инициализироваться значениями, известными вовремя компиляции:11int s z ;const auto arrayS i zeК а к и ранее11ОК, arraySi ze являетсяконстантной копией s zstd : : a rray<in t ,1 1 Ошибка ! Значение arraySizearraySi ze> data; 1 1 при компиляции неизвестноsz;11Проще говоря, все объекты, являющиеся constexpr, являются const, но не все объекты, являющиеся const, являются constexpr.