Почти виртуальный оператор new
Если дан указатель на один из конкретных подтипов запроса, то разместить в хипе дубликат объекта несложно:
NotQuery *pnq; // установить pnq ... // оператор new вызывает // копирующий конструктор NotQuery ... |
NotQuery *pnq2 = new NotQuery( *pnq );
Если же у нас есть только указатель на абстрактный класс Query, то задача создания дубликата становится куда менее тривиальной:
const Query *pq = pnq->op(); |
// как получить дубликат pq?
Если бы позволялось объявить виртуальный экземпляр оператора new, то проблема была бы решена, поскольку автоматически вызывался бы нужный экземпляр. К сожалению, это невозможно: new – статическая функция-член, которая применяется к неструктурированной памяти еще до конструирования объекта класса (см. раздел 15.8).
Но хотя оператор new
нельзя сделать виртуальным, разрешается создать его суррогат, который будет выделять память из хипа и копировать туда объекты, – clone():
class Query { public: virtual Query *clone() = 0; // ... |
};
Вот как он может быть реализован в классе NameQuery:
class NameQuery : public Query { public: virtual Query *clone() // вызывается копирующий конструктор класса NameQuery { return new NameQuery( *this ); } // ... |
};
Это работает правильно, если тип целевого указателя Query*:
Query *pq = new NameQuery( "valery" ); |
Query *pq2 = pq->clone();
Если же его тип равен NameQuery*, нужно привести возвращенный указатель типа Query* назад к типу NameQuery*:
NameQuery *pnq = new NameQuery( "Rilke" ); NameQuery *pnq2 = |
static_cast<NameQuery*>( pnq->clone() );
(Причина, по которой необходимо преобразование типа, объясняется в разделе 19.1.1.)
Как правило, тип значения, возвращаемого реализацией виртуальной функции в производном классе, должен совпадать с типом, возвращаемым ее реализацией в базовом. Исключение, о котором мы уже упоминали, призвано поддержать рассмотренную ситуацию. Если виртуальная функция в базовом классе возвращает значение некоторого типа класса (либо указатель или ссылку на тип класса), то ее реализация в производном может возвращать значение, тип которого является производным от этого класса с открытым типом наследования (то же относится к ссылкам и указателям):
class NameQuery : public Query { public: virtual NameQuery *clone() { return new NameQuery( *this ); } // ... |
Теперь pq2 и pnq2
можно инициализировать без явного приведения типов:
// Query *pq = new NameQuery( "Broch" ); Query *pq2 = pq->clone(); // правильно // NameQuery *pnq = new NameQuery( "Rilke" ); |
Так выглядит реализация clone() в классе NotQuery:
class NotQuery : public Query { public: virtual NotQuery *clone() { return new NotQuery( *this ); } // ... |
Реализации в AndQuery и OrQuery
аналогичны. Чтобы эти реализации clone() работали правильно, в классах NotQuery, AndQuery и OrQuery должны быть явно определены копирующие конструкторы. (Мы займемся этим в разделе 17.6.)