Оно обходится не бесплатно еще и по другой причине. Правила, определяющие инициализацию виртуальных базовых классов, сложнее и интуитивно не так понятны, как правила для невиртуальных базовых классов. Ответственность за инициализацию виртуального базового класса ложится на
Мой совет относительно виртуальных базовых классов (то есть виртуального наследования) прост. Во-первых, не применяйте виртуальных базовых классов до тех пор, пока в этом не возникнет настоятельная потребность. По умолчанию используйте невиртуальное наследование. Во-вторых, если все же избежать виртуальных базовых классов не удается, старайтесь не размещать в них данных. Тогда можно будет забыть о странностях правил инициализации (да, кстати, и присваивания) таких классов. Неспроста интерфейсы Java и. NET, которые во многом подобны виртуальным базовым классам C++, не могут содержать никаких данных.
Теперь рассмотрим следующий интерфейсный класс C++ (см. правило 31) для моделирования физических лиц:
class IPerson {
public:
virtual ~IPerson;
virtual std::string name const = 0;
virtual std::string birthDate const = 0;
};
Пользователи IPerson должны программировать в терминах указателей и ссылок на IPerson, поскольку создавать объекты абстрактных классов запрещено. Для создания объектов, которыми можно манипулировать как объектами IPerson, используются функции-фабрики (опять же см. правило 31), которые порождают объекты конкретных классов, производных от IPerson:
// функция-фабрика для создания объекта Person по уникальному
// идентификатору из базы данных; см. в правиле 18,
// почему возвращаемый тип – не обычный указатель
std::tr1::shared_ptrIPerson makePerson(DatabaseID personIdentifier);
// функция для запроса идентификатора у пользователя
DatabaseID askUserForDtabaseID;
DatabaseID id(askUserForDtabaseID);
std::tr1::shared_ptrIPerson pp(makePerson(id)); // создать объект,
// поддерживающий
// интерфейс IPerson
... // манипулировать *pp
// через функции-члены
// IPerson
Но как makePerson создает объекты, на которые возвращает указатель? Ясно, что должен быть какой-то конкретный класс, унаследованный от IPerson, который makePerson может инстанцировать.
Предположим, этот класс называется CPerson. Будучи конкретным классом, CPerson должен предоставлять реализацию чисто виртуальных функций, унаследованных от IPerson. Можно написать его «с нуля», но лучше воспользоваться уже готовыми компонентами, которые делают большую часть работы. Например, предположим, что старый, ориентированный только на базы данных класс Person-Info предоставляет почти все необходимое CPerson:
class PersonInfo {
public:
explicit PersonInfo(DatabaseID pid)
virtual ~PersonInfo;
virtual const char *theName const;
virtual const char *theBirthDate const;
...
private:
virtual const char *valeDelimOpen const; // ~n`i. 'i`eaea
virtual const char *valeDelimClose const;
...
};
Понять, что этот класс старый, можно хотя бы потому, что функции-члены возвращают const char* вместо объектов string. Но если ботинки подходят, почему бы не носить их? Имена функций-членов класса наводят на мысль, что результат может оказаться вполне удовлетворительным.
Вскоре вы приходите к выводу, что класс PersonInfo был спроектирован для печати полей базы данных в различных форматах, с выделением начала и конца каждого поля специальными строками-разделителями. По умолчанию открывающим и закрывающим разделителями служат квадратные скобки, поэтому значение поля «Ring-tailed Lemur» будет отформатировано так:
[Ring-tailed Lemur]