• Какие операторы и функции имеют смысл для нового типа?Ответ на этот вопрос определяет набор функций, которые вы объявляете в вашем классе. Некоторые из них будут функциями-членами, другие – нет (см. правила 23, 24 и 46).
• Какие стандартные функции должны стать недоступными?Их надо будет объявить закрытыми (см. правило 6).
• Кто должен получить доступ к членам вашего нового типа?Ответ на этот вопрос помогает определить, какие члены должны быть открытыми (public), какие – защищенными (protected) и какие – закрытыми (private). Также вам предстоит решить, какие классы и/или функции должны быть друзьями класса, а также когда имеет смысл вложить один класс внутрь другого.
• Что такое «необъявленный интерфейс» вашего нового типа?Какого рода гарантии могут быть предоставлены относительно производительности, безопасности относительно исключений (см. правило 29) и использования ресурсов (например, блокировок и динамической памяти)? Такого рода гарантии определяют ограничения на реализацию вашего класса.
• Насколько общий ваш новый тип?Возможно, в действительности вы не определяете новый тип. Возможно, вы определяете целое семейство типов. Если так, то вам нужно определять не новый класс, а новый шаблон класса.
• Действительно ли новый тип представляет собой то, что вам нужно?Если вы определяете новый производный класс только для того, чтобы расширить функциональность существующего класса, то, возможно, этой цели лучше достичь простым определением одной или более функций-нечленов либо шаблонов.
На эти вопросы нелегко ответить, поэтому определение эффективных классов – непростая задача. Но при ее должном выполнении определенные пользователями классы C++ дают типы, которые ничем не уступают встроенным и уже оправдывают все ваши усилия.
Что следует помнить
• Проектирование класса – это проектирование типа. Прежде чем определять новый тип, убедитесь, что рассмотрены все вопросы, которые обсуждаются в настоящем правиле.
Правило 20: Предпочитайте передачу по ссылке на const передаче по значению
По умолчанию в C++ объекты передаются в функции и возвращаются функциями по значению (свойство, унаследованное от C). Если не указано противное, параметры функции инициализируются копиями реальных аргументов, а после вызова функции программа получает копию возвращаемой функцией величины. Копии вырабатываются конструкторами копирования. Поэтому передача по значению может оказаться накладной операцией. Например, рассмотрим следующую иерархию классов:
class Person {
public:
Person(); // параметры опущены для простоты
virtual ~Person(); // см. в правиле 7 – почему виртуальный
...
private:
std::string name;
std::string address;
};
class Student: public Person {
public:
Student(); // и здесь параметры опущены
~ Student();
...
private:
std::string schoolName;
std::string schoolAddress;
};
Теперь взгляните на следующий код, где вызывается функция validateStudent, которая принимает аргумент Student (по значению) и возвращает признак его корректности:
bool validateStudent(Student s); // функция принимает параметр
// Student по значению
Student plato; // Платон учился у Сократа
bool platoIsOk = validateStudent(plato); // вызов функции
Что происходит при вызове этой функции?
Ясно, что вызывается конструктор копирования Student для инициализации параметра plato. Также ясно, что s уничтожается при возврате из validate-Student. Поэтому передача параметра по значению этой функции обходится в один вызов конструктора копирования Student и один вызов деструктора Student.
Но это еще не все. Объект Student содержит внутри себя два объекта string, поэтому каждый раз, когда вы конструируете объект Student, вы должны также конструировать и эти два объекта. Класс Student наследует класу Person, поэтому каждый раз, конструируя объект Student, вы должны сконструировать и объект Person. Но объект Person содержит еще два объекта string, поэтому каждое конструирование Person влечет за собой два вызова конструктора string. Итак, передача объекта Student по значению приводит к одному вызову конструктора копирования Student, одному вызову конструктора копирования Person и четырем вызовам конструкторов копирования string. Когда копия объекта Student разрушается, каждому вызову конструктора соответствует вызов деструктора, поэтому общая стоимость передачи Student по значению составляет шесть конструкторов и шесть деструкторов!
Читать дальше
Конец ознакомительного отрывка
Купить книгу