Опубликован: 10.10.2006 | Доступ: свободный | Студентов: 6131 / 467 | Оценка: 4.26 / 3.88 | Длительность: 31:30:00
Лекция 12:

Проектирование и С++

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(); }  // делегирование g()
    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). Если нельзя избежать "взаимных" операций преобразования типов, то нужно преодолевать возникающие столкновения или с помощью явных преобразований (как было показано), или с помощью определения нескольких различных версий бинарной операции (в нашем случае +).

Дарья Федотова
Дарья Федотова
Сергей Березовский
Сергей Березовский

В рамках проф. переподготовки по программе "Программирование"

Есть курсы, которые я уже прошел. Но войдя в курс я вижу, что они не зачтены (Язык Ассемблера и архитектура ЭВМ, Программирование на С++ для профессионалов). Это как?

Роман Островский
Роман Островский
Украина
Оксана Пагина
Оксана Пагина
Россия, Москва