Бьерн Страуструп. Язык программирования С++ Второе дополненное издание



Pdf көрінісі
бет234/256
Дата11.07.2022
өлшемі2,87 Mb.
#37591
1   ...   230   231   232   233   234   235   236   237   ...   256
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->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). Если нельзя избежать "взаимных" операций преобразования типов, 
то нужно преодолевать возникающие столкновения или с помощью явных преобразований (как было 
показано), или с помощью определения нескольких различных версий бинарной операции (в нашем 
случае +). 


Достарыңызбен бөлісу:
1   ...   230   231   232   233   234   235   236   237   ...   256




©emirsaba.org 2024
әкімшілігінің қараңыз

    Басты бет