Бьерн Страуструп.
Язык программирования С++
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). Если нельзя избежать "взаимных" операций преобразования типов,
то нужно преодолевать возникающие столкновения или с
помощью явных преобразований (как было
показано), или с помощью определения нескольких различных версий бинарной операции (в нашем
случае +).
Достарыңызбен бөлісу: