187
до полного вычисления самого большого вмещающего выражения. Если ваш компилятор следует
этому правилу, то строку
{ return *(new Integer(&(value + i.value)); }
можно записать в виде
{ return Integer(value + 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 {
// Промежуточный базовый класс
188
// Традиционная двойная передача в NBase
public:
virtual NBase& operator+=(const Integer&) const = 0;
virtual NBase& operator+=(const Real&) 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, дело обстоит именно так.
Do'stlaringiz bilan baham: |