Язык программирования C++ для профессионалов



Список с принудительной связью - часть 2


void f(const char* s) { slist_base slb; slb.insert(new name(s)); // ... name* p = (name*)slb.get(); // ... delete p; }

Здесь все нормально, но поскольку определение класса slist_base дано через структуру slink, приходится использовать явное приведение типа для преобразования значения типа slink*, возвращаемого функцией slist_base::get(), в name*. Это некрасиво. Для большой программы, в которой много списков и производных от slink классов, это к тому же чревато ошибками. Нам пригодилась бы надежная по типу версия класса slist_base:

template<class T> class Islist : private slist_base { public: void insert(T* a) { slist_base::insert(a); } T* get() { return (T*) slist_base::get(); } // ... };

Приведение в функции Islist::get() совершенно оправдано и надежно, поскольку в классе Islist гарантируется, что каждый объект в списке действительно имеет тип T или тип производного от T класса. Отметим, что slist_base является частным базовым классом Islist. Мы нет хотим, чтобы пользователь случайно натолкнулся на ненадежные детали реализации.

Имя Islist (intrusive singly linked list) обозначает односвязный список с принудительной связью. Этот шаблон типа можно использовать так:

void f(const char* s) { Islist<name> ilst; ilst.insert(new name(s)); // ... name* p = ilst.get(); // ... delete p }

Попытки некорректного использования будет выявлены на стадии трансляции:

class expr : public slink { // ... };

void g(expr* e) { Islist<name> ilst; ilst.insert(e); // ошибка: Islist<name>::insert(), // а нужно name* // ... }

Нужно отметить несколько важных моментов относительно нашего примера. Во-первых, решение надежно в смысле типов (преграда тривиальным ошибкам ставится в очень ограниченной части программы, а именно, в функциях доступа из Islist). Во-вторых, надежность типов достигается без увеличения затрат времени и памяти, поскольку функции доступа из Islist тривиальны и реализуются подстановкой. В-третьих, поскольку вся настоящая работа со списком делается в реализации класса slist_base (пока еще не представленной), никакого дублирования функций не происходит, а исходный текст реализации, т.е. функции slist_base, вообще не должен быть доступен пользователю. Это может быть существенно в коммерческом использовании служебных программ для списков. Кроме того, достигается разделение между интерфейсом и его реализацией, и становится возможной смена реализации без перетрансляции программ пользователя. Наконец, простой список с принудительной связью близок по использованию памяти и времени к оптимальному решению. Иными словами, такой подход близок к оптимальному по времени, памяти, упрятыванию данных и контролю типов и в тоже время он обеспечивает большую гибкость и компактность выражений.

К сожалению, объект может попасть в Islist только, если он является производным от slink. Значит нельзя иметь список Islist из значений типа int, нельзя составить список из значений какого-то ранее определенного типа, не являющегося производным от slink. Кроме того, придется постараться, чтобы включить объект в два списка Islist (§6.5.1).




Содержание  Назад  Вперед