Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
КРАТКИЙ ОБЗОР С.doc
Скачиваний:
1
Добавлен:
26.10.2018
Размер:
2.11 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). Если нельзя избежать "взаимных" операций преобразования типов, то нужно преодолевать возникающие столкновения или с помощью явных преобразований (как было показано), или с помощью определения нескольких различных версий бинарной операции (в нашем случае +).