Чтение онлайн

на главную

Жанры

Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ

Майерс Скотт

Шрифт:

Вышеприведенный класс Person демонстрирует отношение типа «содержит». Объект Person имеет имя, адрес, номера телефона и факса. Нельзя сказать, что человек «есть разновидность» имени или что человек «есть разновидность» адреса. Можно сказать, что человек «имеет» («содержит») имя и адрес. Большинство людей не испытывают затруднений при проведении подобных различий, поэтому путаница между ролями «является» и «содержит» возникает сравнительно редко.

Чуть сложнее провести различие между отношениями «является» и «реализуется посредством». Например, предположим, что вам нужен шаблон

для классов, представляющих множества произвольных объектов, то есть наборов без дубликатов. Поскольку повторное использование – прекрасная вещь, то сразу возникает желание обратиться к шаблону set из стандартной библиотеки. В конце концов, зачем писать новый шаблон, когда есть возможность использовать уже готовый?

К сожалению, реализации set обычно влекут за собой накладные расходы – по три указателя на элемент. Связано это с тем, что множества обычно реализованы в виде сбалансированных деревьев поиска, гарантирующих логарифмическое время поиска, вставки и удаления. Когда быстродействие важнее, чем объем занимаемой памяти, это вполне разумное решение, но конкретно для вашего приложения выясняется, что экономия памяти более существенна. Поэтому стандартный шаблон set для вас неприемлем. Похоже, нужно писать свой собственный.

Тем не менее повторное использование – прекрасная вещь. Будучи экспертом в области структур данных, вы знаете, что среди многих вариантов реализации множеств есть и такой, который базируется на применении связанных списков. Вы также знаете, что в стандартной библиотеке C++ есть шаблон list, поэтому решаете им воспользоваться (повторно).

В частности, вы решаете, что создаваемый вами шаблон Set должен наследовать от list. То есть Set<T> будет наследовать list<T>. В итоге в вашей реализации объект Set будет выступать как объект list. Соответственно, вы объявляете Set следующим образом:

template<typename T> // неправильный способ использования

class Set: public std::list<T> {...}; // list для определения Set

До сих пор все вроде бы шло хорошо, но, если присмотреться, в код вкралась ошибка. Как объясняется в правиле 32, если D является разновидностью B, то все, что верно для B, должно быть верно также и для D. Однако объект list может содержать дубликаты, поэтому если значение 3051 вставляется в list<int> дважды, то список будет содержать две копии 3051. Напротив, Set не может содержать дубликатов, поэтому, если значение 3051 вставляется в Set<int> дважды, множество будет содержать лишь одну копию данного значения. Следовательно, утверждение, что Set является разновидностью list, ложно: ведь некоторые положения, верные для объектов list, неверны для объектов Set.

Из-за этого отношение между этими двумя классами не подходит под определение «является», открытое наследование – неправильный способ моделирования этой взаимосязи. Правильный подход основан на понимании того факта, что объект Set может быть реализован посредством объекта list:

template<typename T> // правильный способ использования list

class Set { // для определения Set

public:

bool member(const T& item) const;

void insert(const T& item);

void remove(const T& item);

std::size_t size const;

private:

std::list<T> rep; //
представление множества

};

Функции-члены класса Set могут опереться на функциональность, предоставляемую list и другими частями стандартной библиотеки, поэтому их реализацию нетрудно написать, коль скоро вам знакомы основы программирования с применением библиотеки STL:

template<typename T>

bool Set<T>::member(const T& item) const

{

return std::find(rep.begin, rel.end, item) != rep.end;

}

template<typename T>

void Set<T>::insert(const T& item)

{

if(!member(item)) rep.push_back(item);

}

template<typename T>

void Set<t>::remove(const T& item)

{

typename std::list<T>::iterator it = // см. в правиле 42

std::find(rep.begin, rep.end, item); // информацию о “typename”

if(it != rep.end) rep.erase(it);

}

template<typename T>

std::size_t Set<T>::size const

{

return rep.size;

}

Эти функции достаточно просты, чтобы стать кандидатами для встраивания, хотя перед принятием окончательного решения стоит еще раз прочитать правило 30.

Стоит отметить, что интерфейс Set лучше отвечал бы требованиям правила 18 (проектировать интерфейсы так, чтобы их легко было использовать правильно и трудно – неправильно), если бы он следовал соглашениям, принятым для STL-контейнеров, но для этого пришлось бы добавить в класс Set столько кода, что в нем потонула бы основная идея: проиллюстрировать взаимосвязь между Set и list. Поскольку тема настоящего правила – именно эта взаимосвязь, то мы пожертвуем совместимостью с STL ради наглядности. Недостатки интерфейса Set не должны, однако, затенять тот неоспоримый факт, что отношение между классами Set и list – не «является» (как это вначале могло показаться), а «реализовано посредством».

Что следует помнить

• Семантика композиции кардинально отличается от семантики открытого наследования.

• В предметной области композиция означает «содержит». В области реализации она означает «реализовано посредством».

Правило 39: Продумывайте подход к использованию закрытого наследования

В правиле 32 показано, что C++ рассматривает открытое наследование как отношение типа «является». В частности, говорится, что компиляторы, столкнувшись с иерархией, где класс Student открыто наследует классу Person, неявно преобразуют объект класса Student в объект класса Person, если это необходимо для вызова функций. Очевидно, стоит еще раз привести фрагмент кода, заменив в нем открытое наследование закрытым:

Поделиться:
Популярные книги

Месть Пламенных

Дмитриева Ольга
6. Пламенная
Фантастика:
фэнтези
6.00
рейтинг книги
Месть Пламенных

Королевская Академия Магии. Неестественный Отбор

Самсонова Наталья
Любовные романы:
любовно-фантастические романы
8.22
рейтинг книги
Королевская Академия Магии. Неестественный Отбор

Имперец. Том 3

Романов Михаил Яковлевич
2. Имперец
Фантастика:
боевая фантастика
попаданцы
альтернативная история
7.43
рейтинг книги
Имперец. Том 3

Бракованная невеста. Академия драконов

Милославская Анастасия
Фантастика:
фэнтези
сказочная фантастика
5.00
рейтинг книги
Бракованная невеста. Академия драконов

Темный Лекарь 7

Токсик Саша
7. Темный Лекарь
Фантастика:
попаданцы
аниме
фэнтези
5.75
рейтинг книги
Темный Лекарь 7

Кодекс Охотника. Книга XIII

Винокуров Юрий
13. Кодекс Охотника
Фантастика:
боевая фантастика
попаданцы
аниме
7.50
рейтинг книги
Кодекс Охотника. Книга XIII

Низший - Инфериор. Компиляция. Книги 1-19

Михайлов Дем Алексеевич
Фантастика 2023. Компиляция
Фантастика:
боевая фантастика
5.00
рейтинг книги
Низший - Инфериор. Компиляция. Книги 1-19

Магия чистых душ 2

Шах Ольга
Любовные романы:
любовно-фантастические романы
5.56
рейтинг книги
Магия чистых душ 2

Курсант: Назад в СССР 11

Дамиров Рафаэль
11. Курсант
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Курсант: Назад в СССР 11

Назад в СССР 5

Дамиров Рафаэль
5. Курсант
Фантастика:
попаданцы
альтернативная история
6.64
рейтинг книги
Назад в СССР 5

Черный Маг Императора 7 (CИ)

Герда Александр
7. Черный маг императора
Фантастика:
фэнтези
попаданцы
5.00
рейтинг книги
Черный Маг Императора 7 (CИ)

Машенька и опер Медведев

Рам Янка
1. Накосячившие опера
Любовные романы:
современные любовные романы
6.40
рейтинг книги
Машенька и опер Медведев

Отмороженный 4.0

Гарцевич Евгений Александрович
4. Отмороженный
Фантастика:
боевая фантастика
постапокалипсис
рпг
5.00
рейтинг книги
Отмороженный 4.0

Покоривший СТЕНУ 6: Пламя внутри

Мантикор Артемис
6. Покоривший СТЕНУ
Фантастика:
фэнтези
попаданцы
рпг
5.00
рейтинг книги
Покоривший СТЕНУ 6: Пламя внутри