Бьерн Страуструп - Язык программирования С++. Главы 5-7 - Виртуальные конструкторы
ОГЛАВЛЕНИЕ
6.7.1 Виртуальные конструкторы
Узнав о виртуальных деструкторах, естественно спросить: "Могут ли конструкторы то же быть виртуальными?" Если ответить коротко - нет. Можно дать более длинный ответ: "Нет, но можно легко получить требуемый эффект".Конструктор не может быть виртуальным, поскольку для правильного построения объекта он должен знать его истинный тип. Более того, конструктор - не совсем обычная функция. Он может взаимодействовать с функциями управления памятью, что невозможно для обычных функций. От обычных функций-членов он отличается еще тем, что не вызывается для существующих объектов. Следовательно нельзя получить указатель на конструктор.
Но эти ограничения можно обойти, если определить функцию, содержащую вызов конструктора и возвращающую построенный объект. Это удачно, поскольку нередко бывает нужно создать новый объект, не зная его истинного типа. Например, при трансляции иногда возникает необходимость сделать копию дерева, представляющего разбираемое выражение. В дереве могут быть узлы выражений разных видов. Допустим, что узлы, которые содержат повторяющиеся в выражении операции, нужно копировать только один раз. Тогда нам потребуется виртуальная функция размножения для узла выражения.
Как правило "виртуальные конструкторы" являются стандартными конструкторами без параметров или конструкторами копирования, параметром которых служит тип результата:
class expr {Виртуальная функция new_expr() просто возвращает стандартно инициализированный объект типа expr, размещенный в свободной памяти. В производном классе можно переопределить функцию new_expr() так, чтобы она возвращала объект этого класса:
// ...
public:
expr(); // стандартный конструктор
virtual expr* new_expr() { return new expr(); }
};
class conditional : public expr {Это означает, что, имея объект класса expr, пользователь может создать объект в "точности такого же типа":
// ...
public:
conditional(); // стандартный конструктор
expr* new_expr() { return new conditional(); }
};
void user(expr* p1, expr* p2)Переменным p3 и p4 присваиваются указатели неизвестного, но подходящего типа.
{
expr* p3 = p1->new_expr();
expr* p4 = p2->new_expr();
// ...
}
Тем же способом можно определить виртуальный конструктор копирования, называемый операцией размножения, но надо подойти более тщательно к специфике операции копирования:
class expr {Параметр deep показывает различие между копированием собственно объекта (поверхностное копирование) и копированием всего поддерева, корнем которого служит объект (глубокое копирование). Стандартное значение 0 означает поверхностное копирование.
// ...
expr* left;
expr* right;
public:
// ...
// копировать `s' в `this'
inline void copy(expr* s);
// создать копию объекта, на который смотрит this
virtual expr* clone(int deep = 0);
};
Функцию clone() можно использовать, например, так:
void fct(expr* root)Являясь виртуальной, функция clone() способна размножать объекты любого производного от expr класса.
{
expr* c1 = root->clone(1); // глубокое копирование
expr* c2 = root->clone(); // поверхностное копирование
// ...
}
Настоящее копирование можно определить так:
void expr::copy(expression* s, int deep)Функция expr::clone() будет вызываться только для объектов типа expr (но не для производных от expr классов), поэтому можно просто разместить в ней и возвратить из нее объект типа expr, являющийся собственной копией:
{
if (deep == 0) { // копируем только члены
*this = *s;
}
else { // пройдемся по указателям:
left = s->clone(1);
right = s->clone(1);
// ...
}
}
expr* expr::clone(int deep)Такую функцию clone() можно использовать для производных от expr классов, если в них не появляются члены-данные (а это как раз типичный случай):
{
expr* r = new expr(); // строим стандартное выражение
r->copy(this,deep); // копируем `*this' в `r'
return r;
}
class arithmetic : public expr {С другой стороны, если добавлены члены-данные, то нужно определять собственную функцию clone():
// ...
// новых членов-данных нет =>
// можно использовать уже определенную функцию clone
};
class conditional : public expression {Функции copy() и clone() определяются подобно своим двойникам из expression:
expr* cond;
public:
inline void copy(cond* s, int deep = 0);
expr* clone(int deep = 0);
// ...
};
expr* conditional::clone(int deep)Определение последней функции показывает отличие настоящего копирования в expr::copy() от полного размножения в expr::clone() (т.е. создания нового объекта и копирования в него). Простое копирование оказывается полезным для определения более сложных операций копирования и размножения. Различие между copy() и clone() эквивалентно различию между операцией присваивания и конструктором копирования ($$1.4.2) и эквивалентно различию между функциями _draw() и draw() ($$6.5.3). Отметим, что функция copy() не является виртуальной. Ей и не надо быть таковой, поскольку виртуальна вызывающая ее функция clone(). Очевидно, что простые операции копирования можно также определять как функции-подстановки.
{
conditional* r = new conditional();
r->copy(this,deep);
return r;
}
void conditional::copy(expr* s, int deep)
{
if (deep == 0) {
*this = *s;
}
else {
expr::copy(s,1); // копируем часть expr
cond = s->cond->clone(1);
}
}