6.6 Реализация — Class

Предыдущая  Содержание  Следующая V*D*V

Class является подклассом Object, поэтому мы можем просто унаследовать методы для сравнения и отображения. Деструктор возвращает пустой указатель, чтобы не дать delete() освободить память, занимаемую описанием класса:

 

static void * Class_dtor (void * _self) {

    struct Class * self = _self;

 

    fprintf(stderr, "%s: cannot destroy class\n", self—>name);

    return 0;

}

 

Вот функция доступа для получения суперкласса из описания класса:

 

const void * super (const void * _self) {

    const struct Class * self = _self;

 

    assert(self && self -> super);

    return self -> super;

}

 

Единственной трудной частью является реализация конструктора Class, потому что это то месте, где инициализируется новое описание класса, где происходит наследование, и где наши четыре основные методы могут быть перезаписаны. Напомним из раздела 6.4, как создается описание нового класса:

 

const void * Any =

                new(Class, "Any", Object, sizeOf(o),

                    differ, Any_differ,

                    0);

 

Это означает, что наш конструктор Class получает имя, суперкласс и размер объекта для описания нового класса. Начинаем перенося их из списка аргументов:

 

static void * Class_ctor (void * _self, va_list * app) {

    struct Class * self = _self;

 

    self -> name = va_arg(* app, char *);

    self -> super = va_arg(* app, struct Class *);

    self -> size = va_arg(* app, size_t);

 

    assert(self -> super);

 

self не может быть нулевым указателем, потому что мы бы иначе не нашли этот метод. super, однако, может оказаться нулём и это было бы очень плохо.

Следующим шагом является наследование. Надо скопировать конструктор и все другие методы из описания суперкласса в super в наше описание нового класса в self:

 

const size_t offset = offsetof(struct Class, ctor);

...

memcpy((char *) self + offset, (char *) self -> super

                + offset, sizeOf(self -> super) - offset);

 

Предполагая, что конструктор является первым методом в struct Class, мы используем макрос ANSI-C offsetof(), чтобы определить начало нашего копирования. К счастью, описание класса в super является подклассом Object и наследует sizeOf(), поэтому можно вычислить, как много байт копировать.

Хотя это решение не вполне безопасно, оно выглядит лучшим компромиссом. Конечно, можно было бы скопировать всю область в super и сохранить после этого новое имя и так далее; тем не менее, всё равно пришлось бы спасать struct Object в начале описания нового класса, так как new() уже сохранила там указатель описания указателя описания класса.

Последняя часть конструктора Class ответственна за перезапись методов, указанных в списке аргументов, переданного в new(). ANSI-C не позволяет присваивать указатели на функции для и из void *, так что необходимо определённое количество приведений :

 

{

    typedef void (* voidf) (); /* указатель родовой функции */

    voidf selector;

    va_list ap = * app;

 

    while ((selector = va_arg(ap, voidf))) {

        voidf method = va_arg(ap, voidf);

 

        if (selector == (voidf) ctor)

            * (voidf *) & self -> ctor = method;

        else if (selector == (voidf) dtor)

            * (voidf *) & self -> dtor = method;

        else if (selector == (voidf) differ)

            * (voidf *) & self -> differ = method;

        else if (selector == (voidf) puto)

            * (voidf *) & self -> puto = method;

    }

    return self;

}}

 

Как мы увидим в разделе 6.10, эту часть списка аргументов лучше распределить среди всех конструкторов класса, чтобы пары селектор / метод могли быть указаны в любом порядке. Мы достигаем этого, больше не увеличивая * app; вместо в va_arg() передаётся копия этого значения ap.

Сохранение методов таким способом имеет несколько последствий: Если в селекторе ни один конструктор класса не подходит, пара селектор / метод молча игнорируется, но, по крайней мере, он не добавляется к описанию класса, которому не принадлежит. Если метод не имеет надлежащего типа, компилятор ANSI-C не обнаружит ошибку, поскольку список аргументов переменной длины и наше приведение предотвращают проверки типов. Здесь мы надеемся на программиста, что селектор соответствует методу, поставляемому вместе с ним, но они должны быть определены как пара и это должно привести к определённой доле доверия.

 

Предыдущая  Содержание  Следующая