Программирование функций шерхан 5

Абсурдно делать сложные проверки безопасности на отладочных запусках. Когда нет доверия к результатам. А затем удалять их в производственных запусках. Когда ошибочный результат может быть дорогим или катастрофическим. Что бы мы подумали о любителе парусного спорта. Который носит спасательный жилет. Тренируясь на суше. Но снимает его. Как только выходит в море?

— Чарльз Энтони Ричард Хор (см. [Hoare73])

В этом разделе дается обзор контрактного программирования (см. [Meyer97], [Mitchell02]и [N1613] для более подробного введения в контрактное программирование).

Читатели, которые уже имеют базовое представление о контрактном программировании. Могут пропустить этот раздел и, возможно. Вернуться к нему после прочтения Учебника.

[Примечание] Примечание

Цель этой библиотеки-не убедить программистов использовать контрактное программирование. Предполагается. Что программы понимают выгоды и компромиссы. Связанные с контрактным программированием. И они уже решили использовать эту методологию в своем коде. Затем эта библиотека стремится быть лучшей и более полной библиотекой контрактного программирования для C++ (без использования программ и инструментов. Внешних по отношению к языку C++ и его препроцессору).

Контрактное программирование характеризуется следующими механизмами утверждения:

  • Предварительныеусловия : Это логические условия. Которые программисты ожидают быть истинными при вызове функции (например. Для проверки ограничений на аргументы функции). Операции, которые логически не имеют предварительных условий (то есть всегда четко определены для всей области их входных данных). Также называются имеющими широкий контракт. Это в отличие от операций. Которые имеют предварительные условия. Которые также называются узким контрактом (обратите внимание. Что операции с действительно узкими контрактами также никогда не вызывают исключений. Потому что реализация этих операций всегда должна быть успешной после проверки ее предварительных условий на истинность).

  • Постусловия: Это логические условия. Которые программисты ожидают. Чтобы быть истинными. Когда функция выходит без создания исключения (например. Чтобы проверить результат и любой побочный эффект. Который может иметь функция). Постусловия могут получить доступ к возвращаемому значению функции (для функций. Не являющихся пустыми). А также к старым значениям (которые являются значениями. Которые выражения имели до выполнения реализации функции).

  • Исключение гарантирует: Это логические условия. Которые программисты исключают. Чтобы быть истинными. Когда функция завершает выбрасывание исключения. Гарантии исключения могут получить доступ к старым значениям (но не к возвращаемому значению функции).
  • Инварианты классов: Это логические условия. Которые программисты ожидают. Чтобы быть истинным после выхода конструктора без создания исключения. До и после выполнения каждой нестатической публичной функции (даже если они создают исключения). До выполнения деструктора (а также после выполнения деструктора. Но только когда деструктор создает исключение).

    Инварианты класса определяют допустимые состояния для всех объектов данного класса. Можно указать другой набор классов инварианты для изменчивых публичных функций. А именно инварианты летучих классов. Также можно указать статические инварианты класса, которые исключаются. Чтобы быть истинными до и после выполнения любого конструктора. Деструктора (даже если он не бросает исключение) и публичной функции (даже если она статична).

  • Субподряд: Это указывает на то. Что предварительные условия не могут быть усилены. В то время как постусловия и инварианты классов не могут быть ослаблены. Когда публичная функция в производном классе переопределяет публичные функции в одном или нескольких его базовых классах (это формально определяется в соответствии с принципом

    замещения).

Фактический код реализации функции. Который остается вне этих утверждений контракта. Часто упоминается как тело функции в контрактном программировании.

Инварианты класса также могут быть использованы для указания базовых гарантий безопасности исключений для объекта (поскольку они проверяются при выходе публичных функций. Даже когда те выбрасывают исключения).

Контрактные утверждения для гарантий исключений могут использоваться для указания строгих гарантий безопасности исключений для данной операции над одним и тем же объектом.

Также распространенным требованием для контрактного программирования является автоматическое отключение проверки контрактов при проверке утверждений из другого контракта (чтобы избежать бесконечной рекурсии при проверке утверждений контрактов).

[Примечание] Примечание

Эта библиотека реализует это требование. Но для того. Чтобы глобально отключить утверждения при проверке другого утверждения. В этой реализации библиотеки должна использоваться некоторая глобальная произвольная переменная.

Эта библиотека автоматически защитит такую глобальную переменную от условий гонки в многопоточных программах. Но это эффективно введет глобальную блокировку в программе (BOOST_CONTRACT_DISABLE_THREADSмакрос можно определить. Чтобы отключить эту глобальную блокировку. Но с риском возникновения в условиях гонки).

В общем случае рекомендуется указывать различные условия контракта с помощью отдельных операторов утверждения и не группировать их вместе в одно условие с помощью логических операторов (

&&,||, и т. Д.). Это происходит потому. Что когда условия контракта программируются вместе в одном утверждении с использованием логических операторов. Может быть неясно. Какое условие на самом деле не удалось. Если все утверждение не выполняется во время выполнения.

Утверждения в стиле C

Ограниченная форма контрактного программирования (обычно некоторая форма проверки предварительных условий и базовых постусловий) может быть достигнута с помощью assertмакроса в стиле Си. Использование assertявляется обычной практикой для многих программистов. Но она страдает от следующих ограничений:

  • assert он не делает различия между предпосылками и постусловиями. В хорошо проверенном производственном коде постусловия обычно могут быть отключены. Доверяя правильности реализации. В то время как предварительные условия все еще могут быть включены из-за возможных изменений в вызывающем коде (например. Постусловия данной библиотеки могут быть отключены после тестирования при сохранении предусловий библиотеки включенными, учитывая. Что будущие изменения в пользовательском коде. Вызывающем библиотеку. Не могут быть ожидаемы).

    С помощью assert невозможно выборочно отключить только постусловия. И все утверждения должны быть отключены сразу.

  • assert требуется вручную запрограммировать дополнительный код для правильной проверки постусловий (в частности. Для обработки функций с несколькими операторами return. Для того чтобы не проверять постусловия. Когда функции генерируют исключения. И для реализации старых значений).
  • assert требуется вручную запрограммировать дополнительный код для проверки инвариантов классов (дополнительные функции-члены. Блоки try и т. Д.).

  • assert не поддерживает субподряд.
  • assert вызовы обычно разбросаны внутри реализаций функций. Поэтому заявленные условия не сразу видны программистам во всей их полноте (как это происходит. Когда утверждения появляются в объявлении функции или. По крайней мере. В самом верху определения функции).

Контрактное программирование не страдает от этих ограничений.

Преимущества

Основное применение контрактного программирования заключается в повышении качества программного обеспечения. [Meyer97] обсуждает. Как контрактное программирование может быть использовано в качестве основного инструмента для написания

правильного программного обеспечения. [Stroustrup94] обсуждает ключевую важность инвариантов классов. А также преимущества и недостатки предварительных и постусловий.

Ниже приводится краткое изложение преимуществ. Связанных с контрактным программированием. Вдохновленным в основном [N1613]:

  • Предварительные условия и постусловия: Используя предварительные условия и постусловия функций. Программисты могут дать точное семантическое описание того. Что требуется функции при ее входе и что она обеспечивает при выходе (если она не создает исключения).

    В частности. Используя старые значения постусловий. Контрактное программирование предоставляет механизм. Позволяющий программистам сравнивать значения выражения до и после выполнения тела функции. Этот механизм он достаточно силен. Чтобы позволить программистам выражать многие ограничения корректности в самом коде. Ограничения. Которые в противном случае должны были бы быть зафиксированы в лучшем случае только неофициально документацией.

  • Инварианты класса: Используя инварианты класса. Программисты могут описать. Чего ожидать от класса и логических зависимостей между членами класса. Задача конструктора состоит в том. Чтобы обеспечить выполнение инвариантов класса при первом создании объекта. Тогда реализация функций-членов может быть в значительной степени упрощена. Поскольку они могут быть написаны, зная. Что инварианты класса удовлетворены. Потому что контрактное программирование проверяет их до и после выполнения каждой публичной функции. Наконец, деструктор удостоверяется. Что инварианты класса хранятся в течение всего срока службы объекта. Проверяя инварианты класса в последний раз перед уничтожением объекта. Инварианты классов также могут быть использованы в качестве критерия хороших абстракций: если невозможно указать инвариант. Это может быть признаком того. Что абстракция дизайна может быть плохой и ее не следовало бы превращать в класс (возможно. Вместо этого было бы достаточно пространства имен).
  • Самодокументированный код: Контракты являются частью исходного кода. Они проверяются во время выполнения. Поэтому они всегда актуальны с самим кодом. Поэтому спецификациям программы. Документированным контрактами. Можно доверять. Чтобы всегда быть в курсе реализации.
  • Более простая отладка: Контрактное программирование может обеспечить мощное средство отладки. Потому что. Если контракты хорошо написаны. Ошибки приведут к тому. Что утверждения контрактов потерпят неудачу именно там. Где проблема впервые возникает. А не на каком-то более позднем этапе выполнения программы явно несвязанным (и часто трудным для отладки) способом. Обратите внимание. Что сбой предварительного условия указывает на ошибку в вызывающей функции. А сбой постусловия вместо этого указывает на ошибку в реализации функции.
  • Более простое тестирование: Контрактное программирование облегчает тестирование. Поскольку контракт естественным образом определяет. Что тест должен проверять. Например, предусловия состояния функции. Входы которой приводят к сбою функции. И постусловия состояния. Выходы которого производятся функцией при успешном выходе (контрактное программирование следует рассматривать как инструмент. Дополняющий и направляющий. Но явно не заменяющий тестирование).
  • Формальное проектирование: Контрактное программирование может помочь сократить разрыв между проектировщиками и программистами. Предоставляя точный и однозначный язык спецификаций в терминах контрактных утверждений. Кроме того. Контракты могут облегчить проверку кода. Прояснив некоторые аспекты семантики и использования кода.
  • Формализовать наследование: Контрактное программирование формализует механизм переопределения виртуальных функций с помощью субподряда. Оправданного принципом замещения. Это держит программистов базового класса под контролем. Поскольку переопределяющие функции всегда должны полностью удовлетворять контрактам своих базовых классов.
  • Заменить защитное программирование: Утверждения контрактного программирования могут заменить проверки защитного программирования, локализуя эти проверки в контрактах и делая код более читаемым.

Конечно, не все формальные спецификации контрактов могут быть утверждены в C++. Например, в C++ невозможно утверждать действительность диапазона итераторов в общем случае (потому что единственный способ проверить. Образуют ли два итератора допустимый диапазон. — это продолжать увеличивать первый итератор до тех пор. Пока он не достигнет второго итератора. Но если диапазон итераторов недействителен. То такой код будет отображать неопределенное поведение или работать вечно вместо того. Чтобы провалить утверждение). Тем не менее. Большое количество утверждений контрактов может быть успешно запрограммировано на C++. Как показано на многочисленных примерах в этой документации и из литературы (например, см.. Сколько STL vector утверждения контрактов могут быть запрограммированы в C++ с помощью этой библиотеки).

Расходы

В общем. Преимущества контрактного программирования приходят за счет производительности. Как подробно обсуждается как в [Stroustrup94], так и в [Meyer97]. В то время как компромиссы между производительностью должны тщательно учитываться в зависимости от конкретной области применения. Качество программного обеспечения не может быть принесено в жертву: трудно увидеть ценность в программном обеспечении. Которое быстро и эффективно дает неверные результаты.

Контрактное программирование негативно влияет на производительность во время выполнения главным образом из-за необходимости дополнительного времени.:

  • Проверьте заявленные условия.
  • Копируйте старые значения. Когда они используются в постусловиях или гарантиях исключений.
  • Вызовите дополнительные функторы. Которые проверяют предварительные условия. Постусловия. Гарантии исключений. Инварианты классов и т. Д. (это может привести к множеству дополнительных вызовов. Особенно при использовании субподряда).
[Примечание] Примечание

В общем случае контракты вводят по крайней мере три дополнительных вызова функтора для проверки предварительных условий. Постусловий и гарантий исключений для любого данного вызова функции. Не являющейся членом. Публичные функции вводят также еще два вызова функций для проверки инвариантов классов (на входе и на выходе). При субподряде эти дополнительные вызовы (некоторые из которых становятся виртуальными вызовами) повторяются для числа функций. Переопределяемых из базовых классов (возможно. Глубоко в дереве наследования). В дополнение к этому. Эта библиотека вводит ряд внутренних вызовов функций для его реализации с целью правильной проверки контрактов.

Чтобы уменьшить влияние на производительность во время выполнения. Программисты могут выборочно отключить проверку во время выполнения некоторых утверждений контракта. Программистам придется принимать решения. Основываясь на компромиссах производительности. Требуемых их конкретными приложениями. Но разумный подход часто заключается в том. Чтобы (см.):

  • Всегда пишите контракты для уточнения семантики проекта. Встраивая спецификации непосредственно в код и делая код самодокументированным.
  • Проверьте предварительные условия. Постусловия. Инварианты класса и, возможно. Даже гарантии исключения во время начального тестирования.
  • Проверьте только предварительные условия (и, возможно. Инварианты класса. Но не постусловия и гарантии исключения) во время тестирования выпуска и для окончательного выпуска.

Этот подход обычно оправдан. Потому что в хорошо проверенном производственном коде проверка реализации тела функции с использованием постусловий редко требуется. Так как функция показала себя правильной во время тестирования. С другой стороны. Проверка аргументов функции с помощью предварительных условий всегда необходима из-за изменений. Которые могут быть внесены в вызывающий код (без необходимости повторного тестирования и повторного выпуска вызываемого кода). Кроме того, постусловия. А также гарантии исключений с соответствующими копиями старых значений часто вычислительно дороже проверяются. Чем предварительные условия и инварианты классов.

Функции. Не являющиеся членами

Вызов функции. Не являющейся членом контракта. Выполняет следующие действия (см.boost::contract::function):

  1. Проверьте предварительные условия функции.
  2. Выполните тело функции.
  3. Если тело не выдало исключение. Проверьте постусловия функции.
  4. В противном случае проверьте гарантии исключения функции.
Частные и защищенные функции

Частные и защищенные функции не обязательно должны удовлетворять инвариантам класса. Поскольку эти функции являются частью реализации класса. А не открытого интерфейса класса. Кроме того, принцип подстановки не применяется к частным и защищенным функциям. Поскольку эти функции недоступны пользователю на вызывающем сайте. Где применяется принцип подстановки.

Поэтому вызовы частных и защищенных функций с контрактами выполняют те же действия. Что и указанные выше для функций. Не являющихся членами (проверка только предварительных условий и постусловий. Без проверки инвариантов классов и без субподряда).

Переопределение Публичных Функций

Давайте рассмотрим публичную функцию в производном классе. Которая переопределяет публичные виртуальные функции. Объявленные его публичными базовыми классами (из-за множественного наследования C++ функция может переопределять более чем один из своих базовых классов). Мы называем функцию в производном классе переопределяющей функцией, а набор базовых классов. Содержащий все переопределенные функции, — переопределенными базисами.

При субподряде переопределенные функции ищутся (во время компиляции) глубоко во всех открытых ветвях дерева наследования (т. Е. проверяются не только прямые публичные родители производного класса. Но и все его публичные бабушки и дедушки и т. Д.). В случае множественного наследования этот поиск также широко распространяется (во время компиляции) на все общедоступные деревья леса множественного наследования (поиск нескольких общедоступных базовых классов выполняется в соответствии с их порядком объявления в списке наследования производного класса). Как обычно с C++ multiple наследование. Этот поиск может привести к нескольким переопределенным функциям и. Следовательно. К субподряду из нескольких общедоступных базовых классов. Обратите внимание. Что для субподряда рассматриваются только общедоступные базовые классы. Поскольку частные и защищенные базовые классы недоступны пользователю на вызывающем сайте. Где применяется принцип замещения.

Вызов переопределяющей публичной функции с контрактом выполняет следующие действия (см.boost::contract::public_function):

  1. Проверьте статические инварианты класса ANDнестатические инварианты класса для всех переопределенных баз, ANDзатем проверьте производные статические ANDнестатические инварианты класса.
  2. Проверьте предварительные условия переопределенных публичных функций из всех переопределенных баз ORдруг с другом, ORа также проверьте предварительные условия переопределенных функций в производном классе.
  3. Выполните тело переопределяющей функции.
  4. Проверьте статические инварианты класса ANDнестатические инварианты класса для всех переопределенных базисов, ANDзатем проверьте статические ANDнестатические инварианты производного класса (даже если тело вызвало исключение).
  5. Если тело не вызвало исключения. Проверьте постусловия переопределенных публичных функций из всех переопределенных баз ANDдруг с другом, ANDа затем проверьте постусловия переопределенных функций в производном классе.
  6. В противном случае проверьте гарантии исключений переопределенных публичных функций из всех переопределенных баз ANDдруг с другом, ANDа затем проверьте гарантии исключений переопределенных функций в производном классе.

Изменчивые публичные функции проверяют статические инварианты классов ANDвместо этого изменчивые инварианты классов. Предварительные условия и постусловия изменчивых публичных функций и инвариантов изменчивого класса обращаются к объекту как volatile.

Инварианты классов проверяются перед предварительными условиями и постусловиями. Поэтому утверждения предварительных условий и постусловий программирования могут быть упрощены при условии. Что инварианты классов уже выполнены (например. Если инварианты классов утверждают. Что указатель не может быть нулевым. То предварительные условия и постусловия могут безопасно разыменовать этот указатель без дополнительной проверки). Аналогично, статические инварианты классов проверяются перед нестатическими инвариантами классов. Поэтому программирование нестатических инвариантов классов (изменчивых и не изменчивых) может быть упрощено. что статические инварианты класса уже удовлетворены. Кроме того, субподряд проверяет контракты открытых базовых классов перед проверкой контрактов производных классов. Поэтому программирование утверждений контрактов производных классов можно упростить. Предполагая. Что контракты открытых базовых классов уже выполнены.

[Примечание] Примечание

В этой документации ANDORуказываются и логика и и или операции. Оцениваемые при коротком замыкании. Например: p AND qистинно тогда и только тогда. Когда оба pи qистинны, но qникогда не вычисляется. Когда pложно; p OR qистинно тогда и только тогда. Когда одно pили qистинно, но qникогда не вычисляется. Когда pистинно.

Как указано выше и в соответствии с принципом замещения, субподряд проверяет предварительные условия, в ORто время как инварианты классов. Постусловия и гарантии исключений проверяются ANDс предварительными условиями. Инвариантами классов. Постусловиями и гарантиями исключений базовых классов соответственно.

Не Переопределяющие Публичные Функции

Вызов нестатической публичной функции с контрактом (который не переопределяет функции ни одного из ее публичных базовых классов) выполняет следующие действия (см.boost::contract::public_function):

  1. Проверьте статические ANDнестатические инварианты класса (но ни один из инвариантов из базовых классов).
  2. Проверьте предварительные условия функций (но ни одно из предварительных условий функций в базовых классах).
  3. Выполните тело функции.
  4. Проверьте класс статических ANDнестатических инвариантов (даже если тело вызвало исключение. Но ни один из инвариантов из базовых классов).
  5. Если тело не вызвало исключение. Проверьте постусловия функции (но ни одно из постусловий из функций в базовых классах).
  6. В противном случае проверьте гарантии исключения функций (но ни одна из гарантий исключения из функций в базовых классах).

Изменчивые публичные функции проверяют статические инварианты классов ANDвместо этого изменчивые инварианты классов. Предварительные и постусловия летучих функций и инвариантов летучего класса обращаются к объекту как volatile.

Инварианты класса проверяются. Поскольку эта функция является частью открытого интерфейса класса. Однако ни один из контрактов базовых классов не проверяется. Поскольку эта функция не переопределяет никаких функций ни одного из общедоступных базовых классов (поэтому принцип замещения в данном случае не требует субподряда).

Статические Публичные функции

Вызов статической публичной функции с контрактом выполняет следующие шаги (см.boost::contract::public_function):

  1. Проверьте статические инварианты класса (но не нестатические инварианты и ни один из инвариантов из базовых классов).
  2. Проверьте предварительные условия функций (но ни одно из предварительных условий из функций в базовых классах).
  3. Выполните тело функции.
  4. Проверьте статические инварианты классов (даже если тело выбросило исключение. Но не нестатические инварианты и ни один из инвариантов из базовых классов).
  5. Если тело не выдало исключения. Проверьте постусловия функции (но ни одно из постусловий функций в базовых классах).
  6. В противном случае проверьте гарантии исключения функций (но ни одна из гарантий исключения из функций в базовых классах).

Инварианты класса проверяются. Потому что эта функция является частью открытого интерфейса класса. Но могут быть проверены только статические инварианты класса (поскольку это статическая функция. Поэтому она не может получить доступ к объекту. Который вместо этого должен был бы проверять нестатические инварианты класса. Изменчивые или нет). Кроме того, статические функции не могут переопределять какую-либо функцию. Поэтому принцип замещения не применяется. И они не заключают субподряд.

Предварительные условия и постусловия статических функций и инвариантов статических классов не могут получить доступ к объекту (поскольку они проверяются из staticфункций-членов).

Вызов конструктора с контрактом выполняет следующие действия (см.boost::contract::constructor_preconditionboost::contract::constructor):

  1. Проверьте предварительные условия конструктора (но они не могут получить доступ к объекту. Поскольку объект еще не построен).
  2. Выполните список инициализации члена конструктора (если он имеется).
    1. Постройте любой базовый класс (открытый или нет) в соответствии с механизмом построения C++. А также проверьте контракты этих базовых конструкторов (в соответствии с шагами. Аналогичными перечисленным здесь).
  3. Проверьте статические инварианты класса (но не нестатические или летучие инварианты класса. Потому что объект еще не построен).
  4. Выполните тело конструктора.
  5. Проверьте статические инварианты класса (даже если тело вызвало исключение).
  6. Если тело не выбросило исключение:
    1. Проверьте нестатические ANDинварианты летучих классов (поскольку объект теперь успешно сконструирован).
    2. Проверьте постусловия конструктора (но они не могут получить доступ к старому значению объектаoldof(*this), поскольку объект не был построен до выполнения тела конструктора).
  7. В противном случае проверьте гарантии исключения конструктора (но они не могут получить доступ к старому значению объектаoldof(*this), потому что объект не был построен до выполнения тела конструктора. Плюс они могут получить доступ только к статическим членам класса. Потому что объект не был успешно построен, учитывая. Что тело конструктора выдало исключение в этом случае).

Предварительные условия конструктора проверяются перед выполнением списка инициализации элементов. Поэтому программирование этих инициализаций может быть упрощено при условии выполнения предварительных условий конструктора (например. Аргументы конструктора могут быть проверены предварительными условиями конструктора перед их использованием для инициализации базовых классов и элементов данных).

Как указано в шаге 2.a. выше. Механизм построения объектов C++ автоматически проверяет контракты базового класса при инициализации этих баз (здесь не требуется явное поведение субподряда).

Вызов деструктора с контрактом выполняет следующие шаги (см.boost::contract::destructor):

  1. Проверка статических инвариантов классов ANDНестатические инварианты ANDлетучих классов.
  2. Выполните тело деструктора (деструкторы не имеют параметров и могут быть вызваны в любое время после построения объекта. Поэтому у них нет предварительных условий).
  3. Проверьте статические инварианты класса (даже если тело выдало исключение).
  4. Если тело не выбросило исключение:
    1. Проверьте постусловия деструктора (но они могут получить доступ только к статическим членам класса и старому значению объектаoldof(*this), поскольку объект был уничтожен после успешного выполнения тела деструктора).
    2. Уничтожьте любой базовый класс (открытый или нет) в соответствии с механизмом уничтожения C++. А также проверьте контракты этих базовых деструкторов (в соответствии с шагами. Аналогичными перечисленным здесь).
  5. Else (даже если деструкторам редко. Если вообще когда-либо. Разрешается создавать исключения в C++):
    1. Проверьте нестатические ANDлетучие инварианты класса (поскольку объект не был успешно разрушен. Поэтому он все еще существует и должен удовлетворять своим инвариантам).
    2. Проверьте гарантии исключения деструктора.

Как указано в шаге 4.b. выше. Механизм уничтожения объектов C++ автоматически проверяет контракты базового класса. Когда деструктор завершает работу без создания исключения (здесь не требуется явное поведение субподряда).

[Примечание] Примечание

Учитывая. Что C++ позволяет деструкторам бросать. Эта библиотека обрабатывает случай. Когда тело деструктора бросает исключение. Как указано выше. Однако, чтобы соответствовать гарантиям безопасности исключений STL и хорошим практикам программирования на C++. Программисты должны реализовать тела деструкторов. Чтобы редко. Если вообще когда-либо. Выбрасывать исключения (на самом деле деструкторы неявно объявлены noexceptв C++11).

Контракты не должны иметь права изменять состояние программы. Поскольку они несут ответственность только за проверку (и не за изменение) состояния программы с целью проверки его соответствия спецификациям. Поэтому контракты должны иметь доступ только к объектам. Аргументам функций. Возвращаемым значениям функций. Старым значениям и всем другим переменным программы в constконтексте (viaconst&,const* const,const volatile, и т. Д.).

Когда это возможно (например. Инварианты классов и старые значения постусловий). Эта библиотека автоматически применяет это ограничение постоянной корректности при использовании во время компиляции const. Однако эта библиотека не может автоматически применять это ограничение во всех случаях (для предварительных условий и постусловий изменяемых функций-членов. Для глобальных переменных и т. Д.). для способов использования этой библиотеки. Которые обеспечивают соблюдение ограничения постоянной корректности во время компиляции (но за счет значительного кода котельной плиты. Программируемого вручную. Поэтому вообще не рекомендуется).

[Примечание] Примечание

В общем, ответственность программистов лежит на утверждениях кода. Которые только проверяют и не изменяют переменные программы.

Контракты являются частью спецификации программы. А не ее реализации. Поэтому контракты в идеале должны быть запрограммированы в объявлениях C++. А не в определениях.

Как правило. Эта библиотека не может удовлетворить этому требованию. Однако, даже когда контракты программируются вместе с телом в определении функции. Пользователям все еще довольно легко идентифицировать и прочитать только контрактную часть определения функции (потому что код контракта всегда должен быть запрограммирован в самом верху определения функции). См. Раздел Реализация Отдельного тела что касается способов использования этой библиотеки для программирования контрактных спецификаций вне реализации тела (но за счет написания одной дополнительной функции для любой данной функции. То это вообще не рекомендуется).

Кроме того. Контракты наиболее полезны. Когда они утверждают условия только с использованием открытых членов (в большинстве случаев необходимость использования непубличных членов для проверки контрактов. Особенно в предварительных условиях. Указывает на ошибку в дизайне класса). Например, вызывающий общедоступную функцию не может в общем случае убедиться. Что предварительные условия функции выполнены. Если утверждения предварительных условий используют закрытые члены. Которые не вызываются вызывающим (поэтому сбой в предварительных условиях не обязательно будет указывать на ошибку в вызывающем объекте. Заданном что вызывающий не смог полностью проверить предварительные условия в первую очередь). Однако, учитывая. Что C++ предоставляет программистам способы обойти ограничения уровня доступа (friendуказатели на функции и т. Д.), эта библиотека оставляет программистам возможность убедиться. Что в контрактных утверждениях используются только открытые члены (особенно в предварительных условиях). ([N1962] следуя тому же подходу. Не ограничивая контракты только использованием открытых членов. Eiffel вместо этого генерирует ошибку времени компиляции. Если предварительные условия утверждаются с использованием непубличных членов.)

Если предварительные условия. Постусловия. Гарантии исключений или инварианты классов либо проверены на ложность. Либо их оценка вызывает исключение во время выполнения. То эта библиотека вызовет определенные функции обработчика сбоев.

По умолчанию эти функции обработчика сбоев печатают сообщение о стандартной ошибке std::cerr(с подробной информацией о сбое). А затем завершают вызов программы std::terminate. Однако с помощьюboost::contract::set_precondition_failure,boost::contract::set_postcondition_failure,boost::contract::set_except_failure, boost::contract::set_invariant_failureи т. Д. программисты могут определить свои собственные функции обработчика сбоев. Которые могут выполнять любое заданное пользователем действие (выбрасывать исключение . Выходить из программы с кодом ошибки и т. Д.,см.

[Примечание] Примечание

В C++ существует ряд проблем с обработчиками сбоя программного контракта. Которые выбрасывают исключения вместо завершения программы. В частности. Деструкторы проверяют инварианты класса. Поэтому они будут бросать. Если программисты изменяют инвариантные обработчики сбоев класса. Чтобы бросить вместо завершения программы. Но в целом деструкторы не должны бросать в C++ (для соблюдения безопасности исключений STL. C++11 неявно noexcept кроме того. Программирование обработчика сбоев. Который вызывает сбои гарантии исключения. Приводит к возникновению исключения (того. Которое сообщает о сбое контракта). В то время как уже существует активное исключение (то. Которое вызвало проверку гарантий исключения в первую очередь). И это заставит C++ завершить программу в любом случае.

Поэтому рекомендуется завершать работу программы хотя бы при отказах контрактов от деструкторов и гарантий исключений (если не во всех остальных случаях отказов контрактов. Как это делается по умолчанию этой библиотекой). Функции обработчика сбоев контракта. Запрограммированные с использованием этой библиотеки. Содержат информацию о неудачном контракте (предварительные условия. Постусловия и т. Д.) и операции. Проверявшей контракт (конструктор. Деструктор и т. Д.), поэтому программисты могут детализированно различать все случаи и решать. Когда это уместно. завершите. Бросьте или выполните какое-либо другое пользовательское действие.

Функции контрактного программирования. Поддерживаемые этой библиотекой. В значительной степени основаны на [N1962] и на языке программирования Eiffel.

В следующей таблице сравниваются функции контрактного программирования среди этой библиотеки, [N1962] (к сожалению. Комитет по стандартам C++ отклонил это предложение. Комментируя отсутствие интереса к добавлению контрактного программирования в C++ в то время. Даже если [N1962] сам по себе является здравым). Более недавнее предложение [P0380] (который был принят в стандарте C++20. Но сожалению. Поддерживает только предварительные и постусловия. В то время как не поддерживает инварианты классов. Старые значения и субподряд). Языки программирования Eiffel и D. Некоторые из пунктов. Перечисленных в этой сводной таблице. Станут более ясными после прочтения остальных разделов данной документации.

Особенность

Эта библиотека

[N1962] Предложение (не принято в C++)

C++20 (см. [P0380])

ISE Eiffel 5.4 (см. [Meyer97])

D (см. [Bright04])

Ключевые слова и спецификаторы

Спецификаторы: precondition, postcondition, invariant,static_invariant, и base_types. Последние три спецификатора отображаются в пользовательском коде. Поэтому их имена можно ссылаться или изменять с помощьюBOOST_CONTRACT_INVARIANT,BOOST_CONTRACT_STATIC_INVARIANT, и BOOST_CONTRACT_BASES_TYPEDEFмакросы соответственно. Чтобы избежать столкновения имен.

Ключевые слова: precondition, postcondition, oldof, и invariant.

Атрибуты: [[expects]]и [[ensures]].

Ключевые слова: require,require else, ensure, ensure then, old, result,do, и invariant.

Ключевые слова: in,out,do,assert, и invariant.

О неудачах контрактов

Распечатайте ошибку std::cerrи вызовите std::terminateее (но ее можно настроить так. Чтобы она вызывала исключения. Выходила с кодом ошибки и т. Д.).

Вызов std::terminate(но может быть настроен для создания исключений. Выхода с кодом ошибки и т. Д.).

Вызов std::abort(но может быть настроен для создания исключений. Выхода с кодом ошибки и т. Д.).

Выбрасывайте исключения.

Выбрасывайте исключения.

Возвращаемые значения в постусловиях

Да, захвачен или передан в качестве параметра (для виртуальных функций) функтору постусловия.

Даpostcondition(result-variable-name).

Да[[ensures result-variable-name: ...]].

Да, resultключевое слово.

Даout(result-variable-name).

Старые значения в постусловиях

Да, BOOST_CONTRACT_OLDOFмакрос и boost::contract::old_ptr(но скопированный до предварительных условий. Если .old(...)он не используется. Как показано в Старых значениях. Скопированных в Теле). Для шаблонов boost::contract::old_ptr_if_copyableпропускает старые копии значений для не копируемых типов и boost::contract::condition_ifпропускает старые копии значений выборочно на основе требований к старым типам выражений (для компиляторов. Которые не поддерживают if constexpr).

Да, oldofключевое слово (скопировано сразу после предварительных условий). (Никогда не пропускается. Даже в шаблонах для не копируемых типов.)

Нет.

Да, oldключевое слово (скопировано сразу после предварительных условий). (Никогда не пропускается. Но все типы копируются в Eiffel.)

Нет.

Инварианты классов

Да, проверено при выходе конструктора. При входе деструктора и броске. А также при входе. Выходе и броске публичной функции. То же самое относится и к инвариантам летучих классов. Статические инварианты класса проверяются при входе. Выходе и броске для конструкторов. Деструкторов и любой (также static) публичной функции.

Да, проверено при выходе конструктора. При входе деструктора и броске. А также при входе. Выходе и броске публичной функции. (Изменчивые и статические инварианты классов не поддерживаются.)

Нет.

Да, проверено на выходе конструктора и вокруг публичных функций. (Летучие и статические инварианты классов неприменимы к Eiffel.)

Да, проверено при выходе конструктора. При входе деструктора и вокруг публичных функций. Однако инварианты не могут вызывать открытые функции (чтобы избежать бесконечной рекурсии. Поскольку D не отключает контракты при проверке других контрактов). (Летучие и статические инварианты классов не поддерживаются, volatileвсе вместе устарели в D.)

Субподряд

Да, также поддерживается субподряд для множественного наследования (BOOST_CONTRACT_BASE_TYPES, BOOST_CONTRACT_OVERRIDE, и boost::contract::virtual_используются для объявления базовых классов. Переопределений и виртуальных публичных функций соответственно).

Да, также поддерживается субподряд для множественного наследования. Но предварительные условия не могут быть субподрядными.

Нет.

ДА.

ДА.

Контракты для чисто виртуальных функций

Да (программируется с помощью внетелесных функций. Как всегда в C++ с чистыми определениями виртуальных функций).

ДА.

Нет (потому что нет субподряда).

Да (контракты для абстрактных функций).

Нет.

Произвольный код в контрактах

Да (но пользователям обычно рекомендуется программировать утверждения только с помощью BOOST_CONTRACT_ASSERTоператоров if-guard и if-guard в контрактах. Чтобы избежать внесения ошибок и дорогостоящего кода в контракты. А также использовать только публичные функции для программирования предварительных условий).

Нет, только утверждения (использование только публичных функций для программирования предварительных условий рекомендуется. Но не предписывается).

Нет, только утверждения (кроме того. Контракты публичных. Защищенных и частных членов могут использовать только другие публичные. Публичные/защищенные и публичные/защищенные/частные члены соответственно).

Нет, только утверждения (кроме того. Только открытые члены могут использоваться в предварительных условиях).

ДА.

Константа-правильность

Нет, применяется только к инвариантам классов и старым значениям (также возможно создание предусловий и постусловий-корректно. Но требует от пользователей программирования определенного количества кода).

ДА.

Да (побочные эффекты в контрактах приводят к неопределенному поведению).

ДА.

Нет, применяется только для инвариантов класса.

Контракты в спецификации

Нет, в определениях функций вместо этого (если программисты вручную не пишут дополнительную функцию для любой заданной функции).

Да (в объявлениях функций).

Да (в объявлениях функций).

ДА.

ДА.

Заказ кода функции

Предпосылки. Постусловия. Гарантии исключений, тело.

Предпосылки. Постусловия, тело.

Предпосылки. Постусловия, тело.

Предпосылки, тело. Постусловия.

Предпосылки. Постусловия, тело.

Отключите проверку утверждений в рамках проверки утверждений (чтобы избежать бесконечной рекурсии при проверке контрактов)

Да, но используйте BOOST_CONTRACT_PRECONDITIONS_DISABLE_NO_ASSERTIONего для отключения no assertion при проверке предварительных условий (см. Также BOOST_CONTRACT_ALL_DISABLE_NO_ASSERTION). (В многопоточных программах это вводит глобальную блокировку, см.BOOST_CONTRACT_DISABLE_THREADS)

Да для инвариантов класса и постусловий. Но предварительные условия не отключают утверждение.

Нет.

ДА.

Нет.

Вызовы вложенных функций-членов

Ничего не отключать.

Ничего не отключать.

Ничего не отключать.

Отключите все утверждения контрактов.

Ничего не отключать.

Отключить проверку контрактов

Да, проверка контракта может быть пропущена во время выполнения путем определения комбинаций BOOST_CONTRACT_NO_PRECONDITIONSBOOST_CONTRACT_NO_POSTCONDITIONSмакросов,BOOST_CONTRACT_NO_INVARIANTS,BOOST_CONTRACT_NO_ENTRY_INVARIANTS, и BOOST_CONTRACT_NO_EXIT_INVARIANTS(полное удаление кода контракта из скомпилированного объектного кода также возможно . Но требует использования макросов. Как показано в разделе Отключить компиляцию контракта).

Да (код контракта также удален из скомпилированного объектного кода. Но детали специфичны для реализации компилятора).

Да (код контракта также удален из скомпилированного объектного кода. Но детали специфичны для реализации компилятора).

Да, но только предопределенные комбинации предварительных условий. Постусловий и инвариантов классов могут быть отключены (контрактный код также удаляется из скомпилированного объектного кода).

ДА.

Уровни утверждений

Да, предопределенные значения по умолчанию. Аудит и аксиома. Кроме того. Программисты также могут определять свои собственные уровни.

Нет (но в предыдущей редакции этого предложения рассматривалось добавление уровней утверждений под названием

Да, предопределенные значения по умолчанию. Аудит и аксиома.

No.

No.

Авторы этой библиотеки проконсультироваться следующие ссылки. Которые реализуют контракт программирования на C++ (но как правило. Лишь ограниченный набор услуг. Или используя инструменты для предварительной обработки другими. Чем c++ препроцессора и внешних. И на сам язык) и другие языки (см. Библиография для получения полного списка всех ссылок консультации в ходе проектирования и развития этой библиотеки):

Ссылка

Язык

Примечания

[Bright04b]

Digital Mars C++

Компилятор Digital Mars C++ расширяет возможности C++. Добавляя поддержку контрактного языка программирования (среди многих других функций).

[Малей99]

C++

Он поддерживает контрактное программирование. Включая субподряд. Но с ограничениями (например. Программистам приходится вручную строить дерево наследования с использованием искусственных параметров шаблона). Он не использует макросы. Но программистам приходится писать вручную значительное количество кода котельной. (Авторы сочли эту работу очень вдохновляющей при разработке первоначальных редакций этой библиотеки. Особенно для ее попытки поддержать субподряд.)

[Lindrud04]

C++

Это поддерживает инварианты классов и старые значения. Но не поддерживает субподряд (контракты указываются в определениях. А не в объявлениях. И утверждения не являются постоянными).

[Tandin04]

C++

Интересно, что эти макросы контрактов автоматически генерируют документацию Doxygen . Но старые значения. Инварианты классов и субподряд не поддерживаются (плюс контракты задаются в определениях. А не в объявлениях и утверждения не являются постоянными).

[Нана]

GCC C++

Это использует макросы. Но работает только на GCC (и. Возможно, Clang. Но не работает на MSVC и т. Д.). Он не поддерживает субподряд. Это требует особой осторожности при программировании постусловий для функций с несколькими операторами return. Кажется. Что он может не проверять инварианты классов. Когда функции выбрасывают исключения (если ENDтолько макрос не делает этого…). (Кроме того. Он предоставляет инструменты для ведения журнала и интеграции с GDB.)

[С2]

C++

При этом используется внешний инструмент предварительной обработки (авторы больше не могли найти код этого проекта для его оценки).

[IContract]

Ява

При этом используется внешний инструмент предварительной обработки.

[Jcontract]

Ява

При этом используется внешний инструмент предварительной обработки.

[CodeContracts]

.NET

Контрактное программирование Microsoft для языков программирования .NET.

[SpecSharp]

С#

Это расширение C# с поддержкой контрактного языка программирования.

[Хром]

Объект Паскаль

Это.ЧИСТАЯ версия Object Pascal и имеет языковую поддержку контрактного программирования.

[СПАРКАда]

Ada

Это Ada-подобный язык программирования с поддержкой контрактного программирования.

Насколько известно авторам. Это единственная библиотека. Которая полностью поддерживает все функции контрактного программирования для C++ (без использования инструментов предварительной обработки. Внешних по отношению к самому языку). В общем:

  • Реализация предварительных условий и постусловий в C++ не является сложной задачей (например. Использование какого-либо объекта RAII).
  • Реализация старых значений postcondition также не слишком сложна (обычно требуется. Чтобы программисты копировали старые значения в локальные переменные). Но уже несколько сложнее гарантировать. Что такие копии не выполняются. Когда postconditions отключены.
  • Реализация инвариантов классов более сложна (особенно если это делается автоматически. Не требуя от программистов вручную вызывать дополнительные функции для проверки инвариантов). Кроме того, все ссылки. Рассмотренные авторами. По-видимому. Не рассматривают статические и летучие функции. Не поддерживающие статические и летучие инварианты соответственно.
  • Реализация субподряда сопряжена со значительной сложностью и. По-видимому. Не поддерживается должным образом ни одной библиотекой C++. Кроме этой (особенно при обработке множественного наследования. Правильном копировании старых значений постусловий во всех переопределенных контрактах глубоко в дереве наследования и правильном сообщении возвращаемого значения постусловиям переопределенных виртуальных функций в базовых классах).