Класс Query должен предоставлять как явный копирующий конструктор, так и явный копирующий оператор присваивания. (Если вам это непонятно, перечитайте раздел 14.6.) Но сначала посмотрим, как почленное копирование по умолчанию происходит без них.
Производный класс NameQuery содержит объект-член типа string и подобъект базового Query. Если есть объект folk класса NameQuery:
NameQuery folk( "folk" );
то инициализация music с помощью folk
NameQuery music = folk;
осуществляется так:
Компилятор проверяет, есть ли в NameQuery явный копирующий конструктор. (Его нет. Поэтому необходимо применить почленную инициализацию по умолчанию.)
Далее компилятор проверяет, содержит ли объект NameQuery подобъекты базового класса. (Да, в нем имеется подобъект Query.)
Компилятор проверяет, определен ли в классе Query явный копирующий конструктор. (Нет, поэтому компилятор применит почленную инициализацию по умолчанию.)
Компилятор проверяет, содержит ли объект Query подобъекты базового класса. (Нет.)
Компилятор просматривает все нестатические члены Query в порядке их объявления. (Если некоторый член не является объектом класса, как, например, _paren и _solution, то в объекте music он инициализируется соответствующим членом объекта folk. Если же является, как, скажем, _loc, то к нему рекурсивно применяется шаг 1. В классе vector определен копирующий конструктор, который вызывается для инициализации music._loc с помощью folk._loc.)
Далее компилятор рассматривает нестатические члены NameQuery в порядке их объявления и находит объект класса string, где есть явный копирующий конструктор. Он и вызывается для инициализации music._name с помощью folk._name.
Инициализация по умолчанию music с помощью folk завершена. Она хороша во всех отношениях, кроме одного: если разрешить копирование по умолчанию члена _solution, то программа, скорее всего, завершится аварийно. Поэтому вместо такой обработки мы предоставим явный копирующий конструктор класса Query. Можно, например, скопировать все разрешающее множество:
Query::Query( const Query &rhs )
: _loc( rhs._loc ), _paren(rhs._paren)
{
if ( rhs._solution )
{
_solution = new setshort;
setshort::iterator
it = rhs._solution-begin(),
end_it = rhs._solution-end();
for ( ; _ir != end_it; ++it )
_solution-insert( *it );
}
else _solution = 0;
}
Однако, поскольку в нашей реализации разрешающее множество вычисляется по мере необходимости, копировать его сразу нет нужды. Назначение нашего копирующего конструктора - предотвратить копирование по умолчанию. Для этого достаточно инициализировать _solution нулем:
Query::Query( const Query &rhs )
: _loc( rhs._loc ),
_paren(rhs._paren), _solution( 0 )
{}
Шаги 1 и 2 инициализации musiс c помощью folk те же, что и раньше. Но на шаге 3 компилятор обнаруживает, что в классе Query есть явный копирующий конструктор и вызывает его. Шаги 4 и 5 пропускаются, а шаг 6 выполняется, как и прежде.
На этот раз почленная инициализация music с помощью folk корректна. Реализовывать явный копирующий конструктор в NameQuery нет необходимости.
Объект производного класса NotQuery содержит подобъект базового Query и член _op типа Query*, который указывает на операнд, размещенный в хипе. Деструктор NotQuery применяет к этому операнду оператор delete.
Для класса NotQuery почленная инициализация по умолчанию члена _op небезопасна, поэтому необходим явный копирующий конструктор. В его реализации используется виртуальная функция clone(), которую мы определили в предыдущем разделе.
inline NotQuery::
NotQuery( const NotQuery &rhs )
// вызывается Query::Query( const Query &rhs )
: Query( rhs )
{ _op = rhs._op-clone(); }
При почленной инициализации одного объекта класса NotQuery другим выполняются два шага:
Компилятор проверяет, определен ли в NotQuery явный копирующий конструктор. Да, определен.
Этот конструктор вызывается для почленной инициализации.
Вот и все. Ответственность за правильную инициализацию подобъекта базового класса и нестатических членов возлагается на копирующий конструктор NotQuery. (Классы AndQuery и OrQuery сходны с NotQuery, поэтому мы оставляем их в качестве упражнения для читателей.)
Почленное присваивание аналогично почленной инициализации. Если имеется явный копирующий оператор присваивания, то он вызывается для выполнения присваивания одного объекта класса другому. В противном случае применяется почленное присваивание по умолчанию.