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

на главную

Жанры

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

Майерс Скотт

Шрифт:

Избегайте использования объектов, если есть шанс обойтись ссылками или указателями. Вы можете определить ссылки и указатели, имея только объявление типа. Определение объектов требует наличия определения типа.

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

class Date; // объявление класса

Date today; //
правильно, необходимость

void clearAppointments(Date d); // в определении Date отсутствует

Конечно, передача по значению – не очень хорошая идея (см. правило 20), но если по той или иной причине вы будете вынуждены ею воспользоваться, это никак не оправдает введения ненужных зависимостей. Не исключено, что возможность объявить функции today и clearAppoinments без определения Date повергла вас в удивление, но на самом деле это не так уж странно. Определение Date должно быть доступно в момент вызова этих функций. Да, я знаю, о чем вы думаете: зачем объявлять функции, которых никто не вызывает? Ответ прост. Дело не в том, что никто не вызывает их, а в том, что их вызывают не все. Например, если имеется библиотека, содержащая десятки объявлений функций, то маловероятно, что каждый пользователь вызывает каждую функцию. Перенося бремя ответственности за предоставление определений класса с ваших заголовочных файлов, содержащих объявления функций, на пользовательские файлы, содержащие их вызовы, вы исключаете искусственную зависимость пользователя от определений типов, которые им в действительности не нужны.

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

Например, если пользователь класса Date захочет объявить функции today и clearAppointments, ему не следует вручную включать опережающее объявление класса Date, как было показано выше. Вместо этого он должен включить директивой #include соответствующий файл с объявлениями:

#include “datefwd.h” // заголочный файл, в котором объявлен

// (но не определен) класс Date

Date today; // как раньше

void clearAppointments(Date d);

Файл с объявлениями назван «datefwd.h» по аналогии с заголовочным файлом <iosfwd> из стандартной библиотеки C++ (см. правило 54). <iosfwd> содержит объявления компонентов iostream, определения которых находятся в нескольких разных заголовках, включая <sstream>, <streambuf>, <fstream> и <iostream>.

Пример <iosfwd> поучителен еще и по другой причине. Из него следует, что совет этого правила относится в равной мере к шаблонным и обычным классам. Хотя в правиле 30 объяснено, что во многих средах разработки программ определения шаблонов обычно находятся в заголовочных файлах, но в некоторых продуктах допускается размещение определений шаблонов и в других местах, поэтому все же имеет смысл предоставить заголовочные файлы, содержащие только объявления, и для шаблонов. <iosfwd> – как раз пример такого файла.

В C++ есть также ключевое слово export, позволяющее отделить объявления шаблонов от их определений. К сожалению, поддержка компиляторами этой возможности ограничена, а практический опыт его

применения совсем невелик. Сейчас еще слишком рано говорить, какую роль будет играть слово export в эффективном программировании на C++. Классы, подобные Person, в которых используется идиома pimpl, часто называют классами-дескрипторами (handle classes). Ответ на вопрос, каким образом работают такие классы, прост: они переадресовывают все вызовы функций соответствующим классам реализаций, которые и выполняют всю реальную работу. Например, вот как могут быть реализованы две функции-члена Person:

#include “Person.h” // поскольку мы реализуем класс Person,

// то должны включить его определение

#include “PersonImpl.h” // мы должны также включить определение класса

// PersonImpl, иначе не сможем вызывать его

// функции-члены; отметим, что PersonImpl имеет

// в точности те же функции-члены, что и

// Person: их интерфейсы идентичны

Person::Person(const std::string& name, const Date& birthday,

const Address& addr)

: pImpl(new Person(name, birthday, addr))

{}

std::string Person::name const

{

return pImpl->name;

}

Обратите внимание на то, как конструктор Person вызывает конструктор Personlmpl (используя new – см. правило 16), и как Person::name вызывает PersonImpl::name. Это важный момент. Превращение Person в класс-дескриптор не меняет его поведения – изменяется только место, в котором это поведение реализовано.

Альтернативой подходу с использованием класса-дескриптора – сделать Person абстрактным базовым классом специального вида, называемым интерфейсным классом. Его назначение – специфицировать интерфейс для производных классов (см. правило 34). В результате он обычно не содержит ни данных-членов, ни конструкторов, но имеет виртуальный деструктор (см. правило 7) и набор чисто виртуальных функций, определяющих интерфейс.

Интерфейсные классы сродни интерфейсам Java и. NET, но C++ не накладывают на интерфейсные классы тех ограничений, которые присущи этим языкам. Например, ни Java, ни. NET не допускают в интерфейсах наличия членов-данных и реализаций функций-членов. C++ этого не запрещает. Большая гибкость C++ в этом отношении может оказаться кстати. Как объясняется в правиле 36, реализация невиртуальных функций должна быть одинаковой для всех классов в иерархии, поэтому имеет смысл реализовать такие функции, как часть интерфейсного класса, в котором они объявлены.

Интерфейсный класс Person может выглядеть примерно так:

class Person {

public:

virtual ~Person;

virtual std::string name const = 0;

virtual std::string birthDate const = 0;

virtual std::string address const = 0;

...

};

Пользователи этого класса должны программировать в терминах указателей и ссылок на Person, потому что невозможно создать экземпляр класса, содержащего чисто виртуальные функции (однако можно создавать экземпляры классов, производных от Person – см. далее). Пользователям интерфейсных классов, как и пользователям классов-дескрипторов, нет нужды проводить перекомпиляцию до тех пор, пока не изменяется интерфейс.

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

Замуж второй раз, или Ещё посмотрим, кто из нас попал!

Вудворт Франциска
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Замуж второй раз, или Ещё посмотрим, кто из нас попал!

Протокол "Наследник"

Лисина Александра
1. Гибрид
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Протокол Наследник

Сердце Дракона. Том 11

Клеванский Кирилл Сергеевич
11. Сердце дракона
Фантастика:
фэнтези
героическая фантастика
боевая фантастика
6.50
рейтинг книги
Сердце Дракона. Том 11

Вперед в прошлое!

Ратманов Денис
1. Вперед в прошлое
Фантастика:
попаданцы
5.00
рейтинг книги
Вперед в прошлое!

Шесть принцев для мисс Недотроги

Суббота Светлана
3. Мисс Недотрога
Фантастика:
фэнтези
7.92
рейтинг книги
Шесть принцев для мисс Недотроги

Хочу тебя любить

Тодорова Елена
Любовные романы:
современные любовные романы
5.67
рейтинг книги
Хочу тебя любить

Огни Аль-Тура. Желанная

Макушева Магда
3. Эйнар
Любовные романы:
любовно-фантастические романы
эро литература
5.25
рейтинг книги
Огни Аль-Тура. Желанная

Начальник милиции 2

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

Мастер...

Чащин Валерий
1. Мастер
Фантастика:
героическая фантастика
попаданцы
аниме
6.50
рейтинг книги
Мастер...

Сумеречный стрелок 8

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

Я до сих пор не князь. Книга XVI

Дрейк Сириус
16. Дорогой барон!
Фантастика:
юмористическое фэнтези
попаданцы
аниме
5.00
рейтинг книги
Я до сих пор не князь. Книга XVI

Обыкновенные ведьмы средней полосы

Шах Ольга
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Обыкновенные ведьмы средней полосы

Метаморфозы Катрин

Ром Полина
Фантастика:
фэнтези
8.26
рейтинг книги
Метаморфозы Катрин

Идеальный мир для Социопата

Сапфир Олег
1. Социопат
Фантастика:
боевая фантастика
рпг
постапокалипсис
6.17
рейтинг книги
Идеальный мир для Социопата