Бьерн Страуструп.
Язык программирования С++
352
смысл случаях такое пересечение пусто. В качестве альтернативного решения можно предоставить
объединение всех множеств операций и предусмотреть динамическую ошибку,
когда в этом
интерфейсе к объекту применяется "несуществующая" операция. Объединение интерфейсов классов,
представляющих множество понятий, называется обширным интерфейсом. Опишем "общий" контейнер
объектов типа T:
class container {
public:
struct Bad_operation { //
класс особых ситуаций
const
char*
p;
Bad_operation(const char* pp) : p(pp) { }
};
virtual void put(const T*)
{
throw
Bad_operation("container::put");
}
virtual
T*
get()
{
throw
Bad_operation("container::get");
}
virtual
T*&
operator[](int)
{
throw
Bad_operation("container::[](int)");
}
virtual T*& operator[](const char*)
{
throw
Bad_operation("container::[](char*)");
}
//
...
};
Все-таки существует мало реализаций, где удачно представлены как индексирование, так и операции
типа списочных, и, возможно, не стоит совмещать их в одном классе.
Отметим такое различие: для гарантии проверки на этапе трансляции в абстрактном типе используются
чистые виртуальные функции, а для обнаружения ошибок на этапе выполнения используются функции
обширного интерфейса, запускающие особые ситуации.
Можно следующим образом описать контейнер, реализованный как простой список с односторонней
связью:
class slist_container : public container, private slist {
public:
void
put(const
T*);
T*
get();
T*&
operator[](int)
{
throw
Bad_operation("slist::[](int)");
}
T*&
operator[](const*
char)
{
throw
Bad_operation("slist::[](char*)");
}
//
...
};
Чтобы упростить обработку динамических ошибок для списка введены операции индексирования.
Можно было не вводить эти нереализованные для списка операции и
ограничиться менее полной
информацией, которую предоставляют особые ситуации, запущенные в классе container:
class vector_container : public container, private vector {
public:
T*&
operator[](int);
T*&
operator[](const
char*);
//
...
};
Если быть осторожным, то все работает нормально:
void f()
{
slist_container
sc;
vector_container
vc;
//
...
}
Бьерн Страуструп.
Язык программирования С++
353
void user(container& c1, container& c2)
{
T* p1 = c1.get();
T* p2 = c2[3];
// нельзя использовать c2.get() или c1[3]
//
...
}
Все же для избежания ошибок при выполнении программы часто приходится использовать
динамическую информацию о типе ($$13.5) или особые ситуации ($$9). Приведем пример:
void user2(container& c1, container& c2)
/*
обнаружение ошибки просто, восстановление - трудная задача
*/
{
try
{
T* p1 = c1.get();
T* p2 = c2[3];
//
...
}
catch(container::Bad_operation& bad) {
// Приехали!
// А что теперь делать?
}
}
или другой пример:
void user3(container& c1, container& c2)
/*
обнаружение ошибки непросто,
а восстановление по прежнему трудная задача
*/
{
slist* sl = ptr_cast(slist_container,&c1);
vector* v = ptr_cast(vector_container, &c2);
if (sl && v) {
T* p1 = c1.get();
T* p2 = c2[3];
//
...
}
else
{
// Приехали!
// А что теперь делать?
}
}
Оба
способа обнаружения ошибки, показанные на этих примерах, приводят к программе с "раздутым"
кодом и низкой скоростью выполнения. Поэтому обычно просто игнорируют возможные ошибки в
надежде, что пользователь на них не натолкнется. Но задача от этого не упрощается,
ведь полное
тестирование затруднительно и требует многих усилий.
Поэтому, если целью является программа с хорошими характеристиками, или требуются высокие
гарантии корректности программы, или, вообще, есть хорошая альтернатива,
лучше не использовать
обширные
интерфейсы.
Кроме
того,
использование
обширного
интерфейса
нарушает
взаимнооднозначное соответствие между классами и понятиями, и тогда начинают вводить новые
производные классы просто для удобства реализации.
Бьерн Страуструп.
Язык программирования С++
354
Достарыңызбен бөлісу: