12.2.7.2 Инкапсуляция
Отметим, что в С++ класс, а не отдельный объект, является той единицей, которая должна быть
инкапсулирована (заключена в оболочку). Например:
class list {
list*
next;
public:
int
on(list*);
};
int list::on(list* p)
{
list* q = this;
for(;;)
{
if (p == q) return 1;
if (q == 0) return 0;
q
=
q->next;
}
}
Здесь обращение к частному указателю list::next допустимо, поскольку list::on() имеет доступ ко всякому
объекту класса list, на который у него есть ссылка. Если это неудобно, ситуацию можно упростить,
отказавшись от возможности доступа через функцию-член к представлениям других объектов,
например:
int list::on(list* p)
{
if (p == this) return 1;
if (p == 0) return 0;
return
next->on(p);
}
Но теперь итерация превращается в рекурсию, что может сильно замедлить выполнение программы,
если только транслятор не сумеет обратно преобразовать рекурсию в итерацию.
12.2.8 Программируемые отношения
Конкретный язык программирования не может прямо поддерживать любое понятие любого метода
проектирования. Если язык программирования не способен прямо представить понятие
проектирования, следует установить удобное отображение конструкций, используемых в проекте, на
языковые конструкции. Например, метод проектирования может использовать понятие делегирования,
означающее, что всякая операция, которая не определена для класса A, должна выполняться в нем с
помощью указателя p на соответствующий член класса B, в котором она определена. На С++ нельзя
выразить это прямо. Однако, реализация этого понятия настолько в духе С++, что легко представить
программу реализации:
class A {
B*
p;
//...
void
f();
void
ff();
};
class B {
//...
Бьерн Страуструп.
Язык программирования С++
329
void
f();
void
g();
void
h();
};
Тот факт, что В делегирует A с помощью указателя A::p, выражается в следующей записи:
class A {
B*
p;
// делегирование с помощью p
//...
void
f();
void
ff();
void g() { p->g(); }
//
делегирование q()
void h() { p->h(); }
//
делегирование h()
};
Для программиста совершенно очевидно, что здесь происходит, однако здесь явно нарушается принцип
взаимнооднозначного соответствия. Такие "программируемые" отношения трудно выразить на языках
программирования, и поэтому к ним трудно применять различные вспомогательные средства.
Например, такое средство может не отличить "делегирование" от B к A с помощью A::p от любого
другого использования B*.
Все-таки следует всюду, где это возможно, добиваться взаимнооднозначного соответствия между
понятиями проекта и понятиями языка программирования. Оно дает определенную простоту и
гарантирует, что проект адекватно отображается в программе, что упрощает работу программиста и
вспомогательных средств. Операции преобразований типа являются механизмом, с помощью которого
можно представить в языке класс программируемых отношений, а именно: операция преобразования
X::operator Y() гарантирует, что всюду, где допустимо использование Y, можно применять и X. Такое же
отношение задает конструктор Y::Y(X). Отметим, что операция преобразования типа (как и конструктор)
скорее создает новый объект, чем изменяет тип существующего объекта. Задать операцию
преобразования к функции Y - означает просто потребовать неявного применения функции,
возвращающей Y. Поскольку неявные применения операций преобразования типа и операций,
определяемых конструкторами, могут привести к неприятностям, полезно проанализировать их в
отдельности еще в проекте.
Важно убедиться, что граф применений операций преобразования типа не содержит циклов. Если они
есть, возникает двусмысленная ситуация, при которой типы, участвующие в циклах, становятся
несовместимыми в комбинации. Например:
class Big_int {
//...
friend Big_int operator+(Big_int,Big_int);
//...
operator
Rational();
//...
};
class Rational {
//...
friend Rational operator+(Rational,Rational);
//...
operator
Big_int();
};
Типы Rational и Big_int не так гладко взаимодействуют, как можно было бы подумать:
void f(Rational r, Big_int i)
{
//...
g(r+i);
// ошибка, неоднозначность:
//
operator+(r,Rational(i))
или
//
operator+(Big_int(r),i)
Бьерн Страуструп.
Язык программирования С++
330
g(r,Rational(i));
// явное разрешение неопределенности
g(Big_int(r),i);
//
еще одно
}
Можно было бы избежать таких "взаимных" преобразований, сделав некоторые из них явными.
Например, преобразование Big_int к типу Rational можно было бы задать явно с помощью функции
make_Rational() вместо операции преобразования, тогда сложение в приведенном примере
разрешалось бы как g(BIg_int(r),i). Если нельзя избежать "взаимных" операций преобразования типов,
то нужно преодолевать возникающие столкновения или с помощью явных преобразований (как было
показано), или с помощью определения нескольких различных версий бинарной операции (в нашем
случае +).
Достарыңызбен бөлісу: |