Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4442.html
 

Перегрузка операторов?

Опубликовано 13.10.2010 г. 23:16

Товарищи, а часто ли вы используете перегрузку операторов при разработке ПО? В принципе для решения повседневных задач это и не требуется. Однако, скажем для работы с графикой это может быть весьма полезно, да и применяется (хотя бы в том же d2d).

Я вот достаточно долго последнее время вынашиваю планы по архитектуре программы которую в скором времени надеюсь начать реализовывать на работе. Суть в том, что это некоторый расчетный комплекс, поэтому там присутствуют всякие типы данных, а-ля матрицы, вектора и т.п. В текущей версии программы, на переписывание которой я и хочу направить свои усилия, присутствует большое количество кода для умножения например одного массива на другой, и т.п. Сколько же времени мы можем сэкономить если заменим весь этот код перегрузкой операторов? Конечно не всегда такой подход будет хорош и т.п. но все же плюсы есть. Вообще в принципе перегрузкой операторов я никогда не занимался, так что насколько я могу понять, то перегружать можно только операторы в действиях со структурами и классами. Т.е если я определю тип данных
type TTestArray : array[1..20] of integer
то для сложения таких переменных у меня инструментов нет? Давайте определим структуру которая будет представлять наш массив. Т.е. допустим мы хотим перегрузить операторы для действий с векторами. Итак. Мы хотим описать вектор в виде структуры, но чтобы для использования нам было все также удобно работать с ним в виде a[i] := .. Для этого в самой структуре мы заведем массив с данными, а также свойство для доступа к этим данным. Данное свойство мы назначим свойством по умолчанию. Подобный метод реализации массива конечно будет медленным в работе, но сейчас нас особо это не волнует. Т.е в отличии от простого массива, где для доступа к элементу требуется всего лишь вычислить его адрес, здесь нам потребуется сначала вызвать метод(процедуру/функцию) доступа, который уже потом будет вычислять адрес, таким образом мы имеем накладные расходы. Также добавим в описание структуры метод для вывода данных на экран. В итоге наш тип массива будет выглядеть следующим образом:
program test;
{$APPTYPE CONSOLE}
type
    TMyArray = record
      strict private
        data : array[1..10] of integer;
        function  getItem(i:byte): integer;
        procedure setItem(i : byte; value : integer);
      public
        procedure print();
        property items[i : byte] : integer read getItem write setItem; default;
    end;

function TMyArray.getItem(i: byte): integer;
begin
    result := data[i];
end;

procedure TMyArray.setItem(i: byte; value: integer);
begin
    data[i] := value;
end;

procedure TMyArray.print;
var i : byte;
begin
    for i in [1..10] do write(data[i],' ');
    writeln;
end;

//-----------------------------------------------
var x,y : TMyArray;
    i : byte;
begin
    for i in[1..10] do x[i] := i;
end.
Давайте теперь представим, что мы хотим реализовать действие, чтобы при присваивании нашей X значения 1, всем элементам внутреннего массива присваивалось значение 1. Т.е некоторая инициализация. Реализуем в секции public оператор неявного приведения Implicit:
class operator TMyArray.Implicit(val: integer): TMyArray;
var i : byte;
begin
    for i in [1..10] do result.data[i] := val;
end;
И теперь мы можем использовать в коде:
    y := 2;
    y.print();
Получив на экране набор из 10ти двоек. Или, например, требуется реализовать сложение двух массивов x+y, которое будет складывать поэлементно значения, а также вычитание из нашего вектора числа, которое будет вычитаться из каждого элемента. Переопределим оператор Add и Subtract:
class operator TMyArray.Add(l, r: TMyArray): TMyArray;
var i : byte;
begin
    for i in [1..10] do result.data[i] := l.data[i] + r.data[i];
end;

class operator TMyArray.Subtract(l: TMyArray; r: integer): TMyArray;
var i: byte;
begin
    for i in [1..10] do result.data[i] := l.data[i] - r;
end;
Теперь наш код
    for i in[1..10] do x[i] := i;
    y := 2;
    (x - 7 + y).print();
будет выводить вполне закономерный ряд [-4;5]. Отметим только порядок следования операндов строго определяется порядком следования аргументов соответствующих перегруженных операторов. Т.е. если можем провести операцию x - 7 то операция 7 - x это совсем иное. Так же весьма удобным наверное будет сравнивать массивы. Например, массив№1 будет больше массива№2 если больше соответствующие суммы элементов.
class operator TMyArray.GreaterThan(l, r: TMyArray): boolean;
var sum : integer;
    i : byte;
begin
    sum := 0;
    for i in [1..10] do inc(sum, l.data[i] - r.data[i]);

    result := (sum > 0);
end;

.....
if x > y then writeln('x > y!');
Итого Delphi предоставляет нам возможность перегрузить 30 различных операторов, полный список которых вы можете увидеть в локальной справке или здесь Конечно, с точки зрения производительности такой подход проигрывает обычному использованию специально написанных соответствующих функций для простых массивов. но согласитесь, ведь в использовании более удобен (: Имеется в виду, конечно, именно такая реализация массива в виде записей с использованием свойств, а не перегрузка операторов. Причем как замечание, сами операции сложения и т.п мы делаем без ущерба для производительности, и потери наши обусловлены внешним доступом, т.е чтением и записью в items. Простой тест вида (ArraySize = 10000)
var x,y,z : TMyArray;
    i : integer;
    count : byte;
    sw : TStopWatch;

type    TSimpleArray = array[1.. ArraySize] of integer;
var     a,b,c : TSimpleArray;

begin
    sw := TStopWatch.StartNew;
    for count in [1..255] do begin
        for i := 1 to ArraySize do x[i] := i;
        y :=  - 1;
        z := x + 7 + y;
    end;
    writeln('elapsed : ', sw.ElapsedMilliseconds);

    //-----------------------------
    sw := TStopwatch.StartNew;
    for count in [1..255] do begin
        for i := 1 to ArraySize do begin
            a[i] := i;
            b[i] := -1;
        end;
        for i := 1 to ArraySize do  c[i] := a[i] + 7 + b[i] ;
    end;
    writeln('elapsed : ', sw.ElapsedMilliseconds);
end.
дает 3-4 кратное преимущество второго варианта с простым использованием массивов.
Метки:  class operator 

Комментарии

qwerty
14.10.2010 в 19:59
а если объявить операторы как inline?
ter
14.10.2010 в 19:32
что то я не думал что get/set методы можно как inline определять (:
разрыв сокращается до 2х раз.
G-[Host]
17.10.2010 в 21:38
А CodeGear как всегда - вводит в язык какую-либо возможность, и не использует ее в стандартной библиотеке. Например, для TStrings оператор + означал бы объединение списков строк, = сравнение и т.д. И программистам было бы нагляднее. А так большинство начинающих и не знает о перегрузке операторов, а столкнись они с ней на практике - знали бы.
Тоже самое и с анонимными методами. В качестве обработчиков событий они намного удобнее, и к тому же совместимы с процедурными типами, ан нет - в RTL и VCL все по-старому.
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно