Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Cpp_Страуструп.doc
Скачиваний:
17
Добавлен:
03.05.2015
Размер:
3.2 Mб
Скачать

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 {

//...

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)

g(r,Rational(i)); // явное разрешение неопределенности

g(Big_int(r),i); // еще одно

}

Можно было бы избежать таких "взаимных" преобразований, сделав

некоторые из них явными. Например, преобразование Big_int к типу

Rational можно было бы задать явно с помощью функции make_Rational()

вместо операции преобразования, тогда сложение в приведенном

примере разрешалось бы как g(BIg_int(r),i). Если нельзя избежать

"взаимных" операций преобразования типов, то нужно преодолевать

возникающие столкновения или с помощью явных преобразований (как было

показано), или с помощью определения нескольких различных версий

бинарной операции (в нашем случае +).