|
Программирование >> Полиморфизм без виртуальных функций в с++
Кенига было принято простое решение: копирование объектов определено как почленное копирование не-статических членов и объектов базовых классов. Согласно этому определению, х=у означает то же, что х. operator= (у). Но здесь есть одно интересное (хотя и не всегда желательное) следствие. Рассмотрим при.мер: class X { /* ... */ }; class Y : public X {/*...*/ }; void g(X X, Y y) { X = y; x.operator=(y): правильно у = X; y.operator=(x): ошибка x - не объект класса Y По умолчанию присваивание объекту класса X - это X: : operator= (const Х&), поэтому х=у - законная операция, ибо Y открыто наследует X. Обычно такую операцию называют вырезанием (slicing), так как х присваивается срез у. Копирующие конструкторы трактуются аналогично. С практическо!! точки зрения вырезание представляется мне сомнительным трюком, но я не вижу способа запретить его, не вводя слишком уж специальных правил. Кроме того, в то время Рави Сетхи как раз просил меня ввести именно такую семантику вырезания , она была нужна ему для теоретических и педагогических целей: если не разрешить присваивание объекта производного класса объекту его открытого базового класса, то это будет единственны.м место.м в С++, где производный объект нельзя использовать вместо базового. Но при этом остается открытой проблема, связанная с операция.ми копирования по умолчанию: указатели-члены копируются, а объекты указания - нет. Это почти всегда неправильно, но запретить операцию нельзя из-за совместимости с С. Однако компилятор легко может выдать предупреждение, если класс с указателем-членом инициализируется при помощи копирующего конструктора или оператора присваивания по умолчанию. Например: class String { char *р; int sz; public: здесь не определены операции копирования (небрежность) void f(const Strings s) { String s2 = s; предупреждение: копируется указатель s2 = s; предупреждение: копируется указатель Се.мантику присваивания и копирования по умолчанию можно назвать поверхностным копированием. Иными словами, копируются члены класса, но не объекты, на которые эти члены указывают. Альтернативу - рекурсивное копирование указываемых объектов (так называемое глубокое копирование) - следует определять явно. Поскольку объекты могут указывать сами на себя, то вряд ли возможно другое решение. В обычном случае не стоит пытаться определять присваивание как глубокое копирование; гораздо лучше определить виртуальную (или невиртуальную) функцию копирования (см. [2nd, стр. 217-220] и раздел 13.7). 11.5. Удобство нотации я хотел позволить пользователю самому определять смысл каждого оператора, если только это разумно и не вступает в серьезное противоречие с предопреде-леипой се.мантикой. Было бы проще разрешить перегрузку всех без исключения операторов либо запретить перегрузку любого оператора, имеющего предопреде-лспную семантику для объектов класса. Принятое в результате компромиссное решение устраивает не всех. Почти все споры и подавляющее большинство сложностей, с которыми мы столкнулись, относятся к операторам, которые не укладываются в привычную схему бинарных или префиксных арифметических операторов. 11.5.1. Умные указатели До версии 2.0 пользователи не могли переопределять оператор разыменования ->. Это осложняло создание классов объектов, ведущих себя как умные указатели. При определении перегрузки операторов -> виделся мне как бинарный оператор со специальными правилами для правого операнда (имени члена). В этой связи вспоминается встреча в компании Mentor Graphics в Орегоне, когда. Джим Ховард (Jim Howard) доказал мне, что я заблуждался. Оператор ->, объяснил он, можно рассматривать как унарный постфиксный оператор, результат которого применяется к имени члена. Пересматривая механизм перегрузки, я воспользовался этой идеей. Если тип значения, возвращаемого функцией operator-> (), используется, то оп должен быть указателем на класс или объект класса, в котором определен operator-> {). Например: struct Y { int m; }; class Ptr { Y* p; . . . public: Ptr(Symbolic ref); -Ptr; Y* operator->() { проверить p return p; Здесь класс Ptr определен так, что его объекты ведут себя как указатели на объекты класса Y с тем отличием, что при каждом обращении производятся некоторые вычисления: void f(Ptr X, Ptr& xr, Ptr* xp) { x->m; x.operator->()->m; то есть x.p->m xr->m; xr.operator->()->m; то есть xr.p->n xp->m; ошибка: у Ptr нет члена m Такие классы особенно полезны в виде шаблонов (см. раздел 15.9.1), [2nd]: template<class Y> class Ptr { /* ... */ }; void f(Ptr<complex> pc, Ptr<Shape> ps) { /* ... */ } Это было понятно уже после реализации перегрузки -> в 1986 г. К сожалению, написать такой код удалось лишь после реализации шаблонов. Для обычных указателей - > - это синоним некоторых применений * и []. Например, для объявления У* р имеют место тождества: р->т == (*р).т ==.р[0].т Как обычно, для определенных пользователем операторов таких гарантий не дается. При желании эквивалентность можно обеспечить: class Ptr { Y* р; public: Y* operator->() { return p; } Y&; operator* 0 { return *p; } Y& operator[](int i) { return p[i]; } ... Перегрузка оператора -> важна для целого класса профа,мм. Причина в том, что косвенное обращение - ключевая концепция, а перегрузка -> дает красивый, прямой и эффективный способ реализации ее в програ.ммах. Еше одно применение оператора -> - это ограниченная, но очень полезная фор.ма реализации делегирования в С++ (см. раздел 12.7). 11.5.2. Умные ссылки Решив разрешить перегрузку оператора ->, я задумался над те.м, можно ли аналогичным образом перегрузить оператор . (точка). В то время мне казались убедительными следующие рассуждения: если obj -это объект класса, то obj .m имеет смысл для каждого члена m этого объекта. Переопределяя встроенные операции, мы не хотим получить мутирующий язык (хотя по очень вески.м причинам это правило нарушено для =, а также для унарного &).
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки. |