Материалы книги получены с http://www.itlibitum.ru/
Самомодификация и переходимость
Невидимый ведущий указатель, как и любой умный указатель, может интерпретироваться как
переходный тип. Если просто заменить указываемый объект каким-нибудь производным классом, вы фактически изменяете тип всей видимой клиенту комбинации. На этом основано решение проблемы оператора +=, которая требует самомодификации левого операнда, а также возможного оперативного изменения типа «на ходу». Если правый операнд 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 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, дело обстоит именно так.
Для такой простой проблемы программа получилась довольно большой. Я не утверждаю, что ваши хлопоты оправдаются во всех проектах. Мое решение в основном предназначено для ситуаций, в которых вы тратите много времени на разработку иерархии классов многократного использования и можете позволить себе потратить время на повышение модульности. Я привел его, поскольку оно соответствует основной идее книги - выжать из С++ все возможное и невозможное и щедро разбросать головоломки, представляющие интерес даже для самых выдающихся экспертов.
Назад Содержание Далее
|