Анимация
JavaScript
|
Главная Библионтека virtual Foo* makeClone() { return new PFoo(foo->makeClone()); } class Bar : public Foo { protected: Bar(Bar&); Конструктор копий public: virtual Foo* makeClone(); Foo* Bar::makeClone() return new Bar(*this); Наконец мы добрались и до применения настоящего конструктора копий. Указатель создает копию - не только свою, но и указываемого объекта. В свою очередь, указываемый объект перекладывает всю тяжелую работу на свой собственный конструктор копий. Обратите внимание: чтобы это стало возможным, мы сделали конструктор копий Foo защищенным. Присваивание Присваивание для невидимых ведущих указателей похоже на присваивание для любых других типов ведущих указателей. И снова мы продолжаем с того места, на котором остановились при присваивании неведущих указателей. class Foo { public: virtual Foo& operator=(const Foo&); В файле foo.cpp class PFoo : public Foo { private: Foo* foo; public: virtual Foo& operator=(const Foo& f) if (this == &f) return *this; delete foo; foo = f.foo->makeClone(); return *this; Foo& Foo::operator=(const Foo&) return *this; Снова о двойной передаче Невидимые указатели позволяют элегантно решить многие проблемы. Одна из таких проблем - улучшенная инкапсуляция двойной передачи, и в том числе решение неприятной проблемы, связанной с оператором +=. Мы объединим двойную передачу с концепцией переходных типов, о которых говорилось давным-давно, в главе 7. Удвоенная двойная передача Итак, давайте попробуем реализовать двойную передачу для невидимых указателей. Этот раздел представляет собой элементарное распространение приемов, которыми мы пользовались без связи с указателями. Первая попытка Сейчас мы сделаем первый заход на арифметические операции с невидимыми указателями. Он работает, но обладает некоторыми ограничениями, на которые следует обратить внимание и должным образом исправить. Чтобы избежать проблем, связанных с возвращением ссылок на временные значения (см. окончание главы 11 ), я перехожу на использование оператора new. Проблемы сборки мусора будут рассматриваться позже. В файле number.h class NBase; Клиентам об этом ничего знать не нужно class Number { protected: Number(const Number&) {} Number() {} public: virtual NBase& operator+(const NBase&) = 0; virtual Number& operator+(const Number&) = 0; И т.д. В файле number.cpp class Integer; class Real; class PNumber : public Number { private: NBase* number; protected: virtual NBase& operator+(const NBase& n) const { return *number + n; } #2 public: PNumber(NBase* n) : number(n) {} virtual Number& operator+(const Number& n) const { return *(new PNumber(&(n + *number))); } #1 class NBase : public Number { Промежуточный базовый класс Традиционная двойная передача в NBase public: virtual NBase& operator+(const Integer&) const = 0; virtual NBase& operator+(const Rea1&) const = 0; И т.д. virtual NBase& operator+(const NBase&) const = 0; virtual Number& operator+(const Number& n) const { return Integer(0); } Заглушка не вызывается class Integer : public NBase { private: int value; protected: virtual NBase& operator+(const Integer& i) const { return *(new Integer(va1ue + i.value)); } #4 public: Integer(int i) : value(i) {} virtual NBase& operator+(const NBase& n) const { return n + *this; } #3 class Real : public NBase { ... }; Как и в исходном варианте двойной передачи, постарайтесь не сосредотачивать взгляд и медленно отодвигайте страницу от носа, пока ну ловите суть происходящего. Ниже подробно расписано, что происходит, когда клиент пытается сложить два Number (а на самом деле - два PNumber, но клиент об этом не знает). Предположим, складываются два Integer: 1. Вызывается операторная функция PNumber: :operator+(const Number&) левого указателя. Выражение переворачивается, и вызывается аналогичная функция правого указателя, при этом аргументом является левый указываемый объект. Однако перед тем, как это случается, функция создает PNumber для результата. 2. Вызывается операторная функция PNumber::operator+(const NBase&) левого указателя. Вызов делегируется оператору + указываемого объекта. 3. Вызывается операторная функция Integer::operator+(const NBase&) правого указываемого объекта. Выражение снова переворачивается. 4. Вызывается операторная функция Integer::opeator+(const Integer&) левого указываемого объекта, где наконец и выполняется реальная операция вычисления суммы. В итоге происходит четыре передачи - две для указателей и две для указываемых объектов. Отсюда и название - удвоенная двойная передача. Мы обходимся без преобразований типов, но зато о существовании NBase приходится объявлять на первых страницах газет. Сокращение до трех передач Если мы разрешим программе «знать», что изначально слева и справа стоят PNumber, и выполним соответствующее приведение типов, количество передач можно сократить до трех: оставить одну передачу для операторной функции PNumber::operator+(const Number&) плюс две обычные двойные передачи. Первый PNumber приходит к выводу, что справа также стоит PNumber, выполняет понижающее преобразование от Number к PNumber, а затем напрямую обращается к указываемому объекту. При этом удается обойтись без PNumber::operator+(const NBase&). Есть и дополнительное преимущество - при должной осторожности можно удалить из файла .h все ссылки на NBase. Проблема заключается в том, что какой-нибудь идиот может вопреки всем предупреждениям породить от Number свой класс, выходящий за пределы вашей тщательно построенной иерархии. Это будет означать, что не все Number будут обязательно «запакованы» в PNumber. Только что показанная методика предотвращает создание производных от Number классов за пределами файла .cpp и даже правильно работает с производными классами без оболочек (Number без PNumber) при условии, что они правильно реализуют схему удвоенной двойной передачи. Как долго результат остается действительным? В показанной выше реализации клиент должен помнить о необходимости избавляться от Number, вызывая delete &aResu1t. Это серьезное ограничение среди прочего усложняет вложенные вычисления, поскольку для всех промежуточных результатов приходится создавать указатель для их последующего удаления. В комитет ANSI поступило предложение (так и не принятое), в соответствии с которым компилятор должен гарантировать, что временная величина в стеке остается действительной 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 [ 57 ] 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |