Помощь в написании студенческих работ
Антистрессовый сервис

Инварианты в программировании

РефератПомощь в написанииУзнать стоимостьмоей работы

Для классов с более сложными операциями расходы на проверки могут быть значительны, поэтому проверки можно оставить только для «поимки» трудно обнаруживаемых ошибок. Обычно полезно оставлять, по крайней мере, несколько проверок даже в очень хорошо отлаженной программе. При всех условиях сам факт определения инвариантов и использования их при отладке дает неоценимую помощь для получения правильной… Читать ещё >

Инварианты в программировании (реферат, курсовая, диплом, контрольная)

Значение членов или объектов, доступных с помощью членов класса, называется состоянием объекта (или просто значением объекта). Главное при построении класса — это: привести объект в полностью определенное состояние (инициализация), сохранять полностью определенное состояние объекта в процессе выполнения над ним различных операций, и в конце работы уничтожить объект без всяких последствий. Свойство, которое делает состояние объекта полностью определенным, называется инвариантом.

Поэтому назначение инициализации — задать конкретные значения, при которых выполняется инвариант объекта. Для каждой операции класса предполагается, что инвариант должен иметь место перед выполнением операции и должен сохраниться после операции. В конце работы деструктор нарушает инвариант, уничтожая объект. Например, конструктор String: String (const char*) гарантирует, что p указывает на массив из, по крайней мере, sz элементов, причем sz имеет осмысленное значение и v[sz-1]==0. Любая строковая операция не должна нарушать это утверждение.

При проектировании класса требуется большое искусство, чтобы сделать реализацию класса достаточно простой и допускающей наличие полезных инвариантов, которые несложно задать. Легко требовать, чтобы класс имел инвариант, труднее предложить полезный инвариант, который понятен и не накладывает жестких ограничений на действия разработчика класса или на эффективность реализации. Здесь «инвариант» понимается как программный фрагмент, выполнив который, можно проверить состояние объекта. Вполне возможно дать более строгое и даже математическое определение инварианта, и в некоторых ситуациях оно может оказаться более подходящим. Здесь же под инвариантом понимается практическая, а значит, обычно экономная, но неполная проверка состояния объекта.

Понятие инварианта появилось в работах Флойда, Наура и Хора, посвященных преди пост-условиям, оно встречается во всех важных статьях по абстрактным типам данных и верификации программ за последние 20 лет. Оно же является основным предметом отладки в C++.

Обычно, в течение работы функции-члена инвариант не сохраняется. Поэтому функции, которые могут вызываться в те моменты, когда инвариант не действует, не должны входить в общий интерфейс класса. Такие функции должны быть частными или защищенными.

Как можно выразить инвариант в программе на С++? Простое решение — определить функцию, проверяющую инвариант, и вставить вызовы этой функции в общие операции. Например:

class String {.

int sz;

int* p;

public:

class Range {};

class Invariant {};

void check ();

String (const char* q);

~String ();

char& operator[](int i);

int size () { return sz; }.

//…

};

void String: check ().

{.

if (p==0 || sz<0 || TOO_LARGE<=sz || p[sz-1]).

throw Invariant;

}.

char& String: operator[](int i).

{.

check (); // проверка на входе.

if (i<0 || i.

check (); // проверка на выходе.

return v[i];

}.

Этот вариант прекрасно работает и не осложняет жизнь программиста. Но для такого простого класса как String проверка инварианта будет занимать большую часть времени счета. Поэтому программисты обычно выполняют проверку инварианта только при отладке:

inline void String: check ().

{.

if (!NDEBUG).

if (p==0 || sz<0 || TOO_LARGE<=sz || p[sz]).

throw Invariant;

}.

Мы выбрали имя NDEBUG, поскольку это макроопределение, которое используется для аналогичных целей в стандартном макроопределении С assert (). Традиционно NDEBUG устанавливается с целью указать, что отладки нет. Указав, что check () является подстановкой, мы гарантировали, что никакая программа не будет создана, пока константа.

NDEBUG не будет установлена в значение, обозначающее отладку. С помощью шаблона типа Assert () можно задать менее регулярные утверждения, например:

template inline void Assert (T expr, X x).

{.

if (!NDEBUG).

if (!expr) throw x;

}.

вызовет особую ситуацию x, если expr ложно, и мы не отключили проверку с помощью NDEBUG. Использовать Assert () можно так:

class Bad_f_arg { };

void f (String& s, int i).

{.

Assert (0<=i && i.

//…

}.

Шаблон типа Assert () подражает макрокоманде assert () языка С. Если i не находится в требуемом диапазоне, возникает особая ситуация Bad_f_arg.

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

Для классов с более сложными операциями расходы на проверки могут быть значительны, поэтому проверки можно оставить только для «поимки» трудно обнаруживаемых ошибок. Обычно полезно оставлять, по крайней мере, несколько проверок даже в очень хорошо отлаженной программе. При всех условиях сам факт определения инвариантов и использования их при отладке дает неоценимую помощь для получения правильной программы и, что более важно, делает понятия, представленные классами, более регулярными и строго определенными. Дело в том, что когда вы создаете инварианты, то рассматриваете класс с другой точки зрения и вносите определенную избыточность в программу. То и другое увеличивает вероятность обнаружения ошибок, противоречий и недосмотров. Две самые общие формы преобразования иерархии классов состоят в разбиении класса на два и в выделении общей части двух классов в базовый класс. В обоих случаях хорошо продуманный инвариант может подсказать возможность такого преобразования. Если, сравнивая инвариант с программами операций, можно обнаружить, что большинство проверок инварианта излишни, то значит класс созрел для разбиения. В этом случае подмножество операций имеет доступ только к подмножеству состояний объекта. Обратно, классы созрели для слияния, если у них сходные инварианты, даже при некотором различии в их реализации.

Показать весь текст
Заполнить форму текущей работой