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