Форум: "Потрепаться";
Текущий архив: 2005.03.20;
Скачать: [xml.tar.bz2];
ВнизПерегрузка операторов в Pascal Найти похожие ветки
← →
dr Tr0jan (2005-02-26 15:22) [0]Возможно ли перегрузить стандартны оператор в Borland Pascal, Object Pascal, Delphi?
Если можно, то как?
← →
Просто Джо © (2005-02-26 15:25) [1]В Делфи, АФАИР, низзя. Никак. Можно во FreePascal, кажется.
← →
TUser © (2005-02-26 15:27) [2]Судя по документации, во FreePascal"е можно. Синтаксис надо посмотреть в доке, я его наизусть не помню.
И не стал бы я этим пользоваться. Совместимость с Delphi теряется, а выгоды незначительные. Проще писать все в префиксной записи, вызывая функции.
← →
kaZaNoVa © (2005-02-26 15:40) [3]не тормози, перезагрузи .. )
← →
dr Tr0jan (2005-02-26 15:58) [4]> Проще писать все в префиксной записи, вызывая функции.
Спасибо, дельный совет.
Да мне вообще-то это надо было в Borland Pascal"e. Вот постфиксной попробую.
← →
вредитель © (2005-02-26 17:58) [5]...а выгоды незначительные
Перегрузка операторов очень помогает медитативным практикам.
Дабы не быть голословным, приведу отрывок из книго Лдефри Элджера с шуточным названием "C++" (надеюсь он меня простит), ибо не смогу объяснить этого лучше, чем он.Множественная передача
Самый распространенный пример гомоморфной иерархии — набор классов,
соответствующих различным видам чисел: целым, комплексным, вещественным и
т.д. Класс-предок такой иерархии может называться Number и иметь интерфейс
следующего вида:
class Number {
public:
virtual Number operator+(const Number&) = 0;
virtual Number operator-(const Number&) = 0;
// И т.д.
};
class Integer : public Number {
private:
int i;
public:
Integer(int x) : i(x) {}
virtual Number operator+(const Number&);
// И т.д.
};
На бумаге все выглядит проще, чем в действительности. Как реализовать
Integer::operator+(Number&), если нам не известно, что в скобках находится
вовсе не Number, а некоторый производный класс? Для каждой пары типов,
участвующих в сложении, существует свой алгоритм. Суммирование Complex +
Integer отличается от Integer + Real, которое, в свою очередь, отличается
от Integer + ArbitraryPrecisionNumber. Как программе разобраться, какой из
алгоритмов следует использовать? Что-что? Кто сказал: «Запросить у
аргумента оператора + его настоящий тип»? Немедленно встаньте в угол.
class Number {
protected:
int type; // Хранит информацию о настоящем типе
int TypeOf() { return type; }
// И т.д.
};
// Где-то в программе
switch (type) {
case kInteger: ...
case kComplex: ...
}
Именно этого знания типов мы постараемся избежать. Кроме того, все прямые
реализации подобных схем не отличаются особой элегантностью. Вы
когда-нибудь видели код, генерируемый компилятором для конструкции
switch/case? Ни красоты, ни эффективности. Вместо этого мы объединим знания
компилятора о типах с чудесами современной технологии — v-таблицами.
Двойная передача
В обобщенном виде задачу можно представить в виде матрицы, строки которой
соответствуют типам левого операнда, а столбцы - всевозможным типам правого
операнда. В каждой ячейке матрицы находится конкретный алгоритм для
обработки сочетания типов. Чаще всего такая ситуация возникает для
гомоморфных иерархий вроде нашей, но вообще типы левого операнда не обязаны
совпадать с типами правого операнда. Конечно, возможны силовые решения -
например, запрятать в каждом экземпляре сведения о его типе. Однако более
элегантное решение (и обычно более эффективное) решение носит название
двойной передачи (double dispatch).
class Number {
protected:
// Диспетчерские функции для оператора +
virtual Number& operator+(const Integer&) = 0;
virtual Number& operator+(const Complet&) = 0;
// Ит.д. для всех производных типов
public:
virtual Number& operator+(const Number&) = 0;
virtual Number& operator-(const Number&) = 0;
// Ит.д.
};
class Integer : public Number {
private:
int I;
protected:
virtual Number& operator+(const Integer&);
virtual Number& operator+(const Complex&);
public:
Integer(int x) : i(x) {}
virtual Number& operator+(const Number&);
// Ит.д.
};
Number& Integer::operator+(const Number& n)
{
return n + *this; // Поменять местами левый и правый операнд
}
Number& Integer::operator+(const Integer& n)
{
// Ниже приведен псевдокод
if (i + n.i слишком велико для int) {
return ЦелоеСПовышеннойТочностью
}
else return Integer(i + n.i);
}
С этим фрагментом связана одна нетривиальная проблема, к которой мы
вернемся позже, а пока сосредоточьте все внимание на концепции. Она похожа
на стереограмму - чтобы скрытая картинка проявилась, вам придется
расслабить глаза и некоторое время рассматривать код. Когда клиент пытается
сложить два Integer, компилятор передает вызов Integer::operator+(),
поскольку operator+(Number&) является виртуальным - компилятор правильно
находит реализацию производного класса. К моменту выполнения
Integer::operator+(Number&) настоящий тип левого операнда уже известен,
однако правый операнд все еще остается загадкой. Но в этот момент наступает
второй этап двойной передачи: return n + *this. Левый и правый операнды
меняются местами, а компилятор приступает к поискам v-таблицы n. Однако на
этот раз он ищет переопределение Number::operator+(Integer&), так как он
знает, что *this в действительности имеет тип Integer. Это приводит к
вызову Integer::operator+(Integer&), поскольку типы обоих операндов
известны и можно наконец произвести вычисления. Если вы так и не поняли,
что же происходит, прогуляйтесь на свежем воздухе и попробуйте снова, пока
не поймете. Возможно, вам поможет следующая формулировка: вместо
кодирования типа в целой переменной мы определили настоящий тип Number с
помощью v-таблицы. Такое решение не только элегантно. Вероятно, оно еще и
более эффективно, чем те, которые приходили вам в голову. Скажем,
приходилось ли вам видеть код, генерируемый компилятором для конструкции
switch/case? Он некрасив и вдобавок куда менее эффективен, чем
последовательное индексирование двух v-таблиц.
Несмотря на всю элегантность, двойная передача довольно дорого обходится по
объему кода и сложности:
o Если у вас имеется m производных классов и n
операторов, то каждый производный класс должен содержать m*(n+1)
виртуальных функций, да еще столько же чисто виртуальных заглушек в
классе-предке. Итого мы получаем (m+1)*m*(n+1) диспетчерских функций. Для
всех иерархий, кроме самых тривиальных, это довольно много.
o Если оператор не является коммутируемым (то есть ему нельзя передать
повторный вызов с аргументами, переставленными в обратном порядке), это
число удваивается, поскольку вам придется реализовать отдельные функции для
← →
вредитель © (2005-02-26 17:58) [6]
двух вариантов порядка аргументов. Например, y/x - совсем не то же, что
x/y; вам понадобится оператор / и специальная функция DivideInto для
переставленных аргументов.
o Клиенты базового класса видят все устрашающие защищенные функции, хотя
это им совершенно не нужно.
Тем не менее, в простых ситуациях двойная передача оказывается вполне
разумным решением - ведь проблема, как ни крути, достаточно сложна.
Специфика ситуации неизбежно требует множества мелких фрагментов кода.
Двойная передача всего лишь заменяет большие, уродливые, немодульные
конструкции switch/case более быстрой и модульной виртуальной
диспетчеризацией. Как правило, количество функций удается сократить, но при
этом приходится в той или иной степени идти на компромисс с нашим строгим
правилом - никогда не спрашивать у объекта, каков его настоящий тип.
Некоторые из этих приемов рассматриваются ниже. Видимость производных
классов для клиентов Number тоже удается ликвидировать минимальной ценой;
об этом будет рассказано в главе 12. Как и во многих проблемах дизайна в
С++, в которых задействованы матрицы операций, вам придется на уровне
здравого смысла решить, стоит ли повышать модульность за счет
быстродействия или объема кода.Гетероморфная двойная передача
Двойная передача обычно возникает в ситуациях, когда оба аргумента
происходят от общего предка, но это не обязательно. Левый и правый операнды
могут принадлежать к разным классам, не имеющим общего предка.
Один из моих любимых примеров относится к обработке событий в графических
средах. Существует множество возможных событий: операции и мышью, события
от клавиатуры, события операционной системы и даже такая экзотика, как
распознавание голоса или световое перо. С другой стороны, в
пользовательский интерфейс обычно входят разнообразные виды, панели или
окна (терминология зависит от операционной системы и используемого языка) -
внешние окна с заголовками и кнопками закрытия, поля для редактирования
текста и области, в которых можно рисовать красивые картинки. Для каждой
комбинации конкретного события с конкретным типом вида может потребоваться
уникальная реализация. Возникает та же проблема, что и с иерархией чисел,
хотя на этот раз события и виды не имеют общего базового класса. Тем не
менее, методика двойной передачи все равно работает.
class Event { // Чисто виртуальный базовый класс для событий
public:
virtual void Process(View* v) = 0;
};
class MouseClick : public Event {
public:
virtual void Process(View* v) { v->Process(*this); }
};
class View { // Чисто виртуальный базовый класс для видов
public:
virtual void Process(MouseClick& ev) = 0;
virtual void Process(Keystroke& ev) = 0;
// Ит.д.
};
Хотя на первый взгляд кажется, что проблема отличается от иерархии Number,
присмотритесь повнимательнее. Реализация функции Process() класса Event
всего лишь "разворачивает" операцию и перенаправляет вызов. Поскольку
функция Event::Process() является виртуальной, когда дело доходит до класса
View, точный тип Event уже известен, и компилятор вызывает правильную
перегруженную версию View::Process().
Каждый раз, когда вам захочется забить код типа в переменную класса, чтобы
узнать, с каким производным классом вы имеете дело, подумайте, нельзя ли
переложить хлопоты на компилятор с помощью двойной передачи (или одного из
описанных ниже вариантов).
← →
вредитель © (2005-02-26 18:00) [7]книго Лдефри Элджера
Простите
должно быть: книги Джефри Элджера
← →
jack128 © (2005-02-26 20:11) [8]AFAIK в дельфи8 и дельфи2005(возможно только для .NET - платформы) перегрузка операторов есть
← →
Mystic © (2005-02-26 20:22) [9]Лично моя точка зрения:
Если после перегрузке в выражениях мы используем 1-2 оператора, то функциональная запись понятнее. А если больше, то проще написать свой интерпретатор, не ограниченый синтаксисом того языка, который мы используем.
← →
Piter © (2005-02-26 22:51) [10]вредитель © (26.02.05 17:58) [5]
MF используешь? :)
← →
вредитель © (2005-02-26 23:17) [11]MF используешь? :)
CodeGenie 3.11
А что есть MF?
← →
вредитель © (2005-02-26 23:23) [12]Ой, недогнал. А как вы догадались?
← →
GuAV © (2005-02-26 23:40) [13]Можно использовать варианты своих типов. Для них возможно переопределение операторов.
← →
VMcL © (2005-02-27 00:30) [14]>>GuAV © (26.02.05 23:40) [13]
Дополню: начиная с D6.
← →
kaZaNoVa © (2005-02-27 00:32) [15]вредитель © (26.02.05 23:23) [12]
так он встроенный детктор юзает ..типа он онлайн, а Питер уже всё о тебе знает)
← →
kaZaNoVa © (2005-02-27 00:33) [16]kaZaNoVa © (27.02.05 0:32) [15]
упс .. у меня пол 4-го, всё перепутал ..
оффтопик сплошной
← →
Piter © (2005-02-27 01:15) [17]вредитель © (26.02.05 23:23) [12]
Ой, недогнал. А как вы догадались?
по тому, как автоматически разбит твой пост [5] и [6] :)
Человек бы не стал так разбивать :)
← →
TUser © (2005-02-27 07:58) [18]Книга, судя по всему, в эл. виде есть. Где качал?
← →
вредитель © (2005-02-27 10:13) [19]Книга, судя по всему, в эл. виде есть. Где качал?
Точно не помню, но вот один раз наткнулся на это:
http://pv.bstu.ru/?topic=oop
Еще Голуба почитайте.
← →
wicked © (2005-02-27 15:51) [20]
> Еще Голуба почитайте
Пашку, что-ли?... ;)
← →
вредитель © (2005-02-27 15:59) [21]Пашку, что-ли?... ;)
Неа. Алена. Тоже шутник, как и Элджер.
Страницы: 1 вся ветка
Форум: "Потрепаться";
Текущий архив: 2005.03.20;
Скачать: [xml.tar.bz2];
Память: 0.53 MB
Время: 0.052 c