Программирование >>  Синтаксис инициирования исключений 

1 ... 55 56 57 [ 58 ] 59 60 61 ... 82


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 поступило предложение (так и не принятое), в соответствии с которым компилятор должен гарантировать, что временная величина в стеке остается действительной



до полного вычисления самого большого вмещающего выражения. Если ваш компилятор следует этому правилу, то строку

{ return *(new Integer(&(va1ue + i.value)); }

можно записать в виде

{ return Integer(va1ue + i.value); }

Аналогично создается и PNumber. Возвращаемое значение будет оставаться действительным внутри вычисляемого выражения. Любая ссылка, которая может существовать за пределами вмещающего выражения, должна быть получена вызовом функции makeClone(). Эта функция создает PNumber в куче или присваивает другой Number виртуальным оператором = для невидимых ведущих указателей, о которых говорилось выше. Чтобы ликвидировать эти раздражающие мелкие утечки памяти, можно воспользоваться приемами уплотнения и сборки мусора, рассмотренными в части 4.

Самомодификация и переходимость

Невидимый ведущий указатель, как и любой умный указатель, может интерпретироваться как переходный тип. Если просто заменить указываемый объект каким-нибудь производным классом, вы фактически изменяете тип всей видимой клиенту комбинации. На этом основано решение проблемы оператора +=, которая требует самомодификации левого операнда, а также возможного оперативного изменения типа на ходу . Если правый операнд Complex складывается с левым операндом Integer, тип левого операнда приходится менять.

В файле number.h

class NBase; Клиентам об этом ничего знать не нужно

class Number {

protected:

Number(const Number&) {}

Number() {} public:

virtual NBase& AddTo(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& AddTo(const NBase& n) const { return number->AddTo(n); } #2

public:

PNumber(NBase* n) : number(n) {}

virtual Number& operator+(const Number& n) const

number = &(n.AddTo(*number)); #1 - замена return *this;

class NBase : public Number { Промежуточный базовый класс



Традиционная двойная передача в NBase public:

virtual NBase& operator+=(const Integer&) const = 0; virtual NBase& operator+=(const Rea1&) const = 0; И т.д.

virtual NBase& AddTo(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

if (value + i.value достаточно мало) { value += i.value; return *this;

else {

ArbitraryPrecisionInteger api(value); api += i.value; delete this; return api;

public:

Integer(int i) : value(i) {}

virtual NBase& AddTo(const NBase& n) const

{ return n + *this; } #3

class Real : public NBase { ... };

Все как и раньше, разве что операторы + превратились в +=, а двойная передача теперь проходит через +=(левый, правый) и AddTo(правый , левый), чтобы мы могли различать два порядка аргументов. Это важно, поскольку в конечном счете мы хотим заменить указываемый объект левого операнда новым. Это происходит в двух местах:

1. Операторная функция PNumber::operator+=(const Number&) автоматически заменяет число полученным новым значением.

2. Операторная функция Integer::operator+=(const Integer&) возвращает управление, если ей не приходится изменять тип; в противном случае после удаления своего объекта она возвращает новый объект другого типа.

По вполне понятным причинам я назову вторую из этих функций заменяющей. Заменяющие функции обладают одной экзотической (если не выразиться сильнее) особенностью: нельзя рассчитывать, что адрес объекта перед вызовом остается действительным и после вызова. Разумеется, пользоваться этим обстоятельством можно лишь в том случае, если эту логику удастся запрятать в самую глубокую и темную дыру, чтобы никто в нее не сунулся, но если это удается сделать, хлопотные алгоритмы невероятно упрощаются.

Показанный пример надежно работает, пока PNumber действует как ведущий указатель и пока можно гарантировать, что ни один объект, производный от NBase, не будет существовать без ссылающегося на него PNumber. В нашем случае, когда все прячется в файле .cpp, дело обстоит именно так.



1 ... 55 56 57 [ 58 ] 59 60 61 ... 82

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика