185
Удвоенная двойная передача
Итак, давайте попробуем реализовать двойную передачу для невидимых указателей. Этот раздел
представляет собой элементарное распространение приемов, которыми мы пользовались без связи с
указателями.
Первая попытка
Сейчас мы сделаем первый заход на арифметические операции с невидимыми указателями. Он
работает, но обладает некоторыми ограничениями, на которые следует обратить внимание и должным
образом исправить. Чтобы избежать проблем, связанных с возвращением ссылок на временные
значения (см. окончание главы 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 Real&) const = 0;
//
И т.д.
virtual NBase& operator+(const NBase&) const = 0;
virtual Number& operator+(const Number& n) const
{ return Integer(0); }
// Заглушка не вызывается
};
class Integer : public NBase {
186
private:
int
value;
protected:
virtual NBase& operator+(const Integer& i) const
{ return *(new Integer(value + 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 &aResult
. Это серьезное ограничение среди прочего усложняет вложенные
вычисления, поскольку для всех промежуточных результатов приходится создавать указатель для их
последующего удаления. В комитет ANSI поступило предложение (так и не принятое), в соответствии
с которым компилятор должен гарантировать, что временная величина в стеке остается действительной
Do'stlaringiz bilan baham: |