Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 64
Текст из файла (страница 64)
Временные объекты и раздельные циклы Т орегасог[] (в1хе Г Ых) сопле ( геецгп вгогаде[1с)х]; ) Тй орегасог[] (в1зе с 1с)х) ( гегигп зсогаде[1с)х]; ) ргосесеес): // Инициализация значений конструктором по умолчанию чоЫ 1пйе() ( аког (вххе е 1с]х = О; Ых < вхзе(); ++1с)х) ( веогаде[1дх] = Т(); ) ) // Копирование значений иэ другого массива чоЫ сору (ЯАггау<Т> сопвей ог1д) ( аввеге(в1хе() ==ог1д.в1ге() ); аког(в1зе с 1с)х = О; 1дх < в1хе(); ++Ых) ( веогаде[Ых] = ог1д.веогаде[Ых]; рг1чаее: Т* всогаде) в1зе е вгогаде в1хе; // Хранилище элементов // Количество элементов Числовые операторы можно закодировать, как показано ниже.
// ехрггщр1/ваггауорв1.)хрр ЯАгхау<Т> гевц1Г(а.в1хе())р аког(в1хе г К = О) К < а.вххе(); ++К) ( гевц1г[К] = а[)с]+Ь[)с]) гесцгп гевц1г; // Умножение двух объектов ЯАггау сещр1аее<еурепаще Т> ЯАггау<Т> орегагог*(ЯАггау<Т> сопвсй а, ЯАггау<Т> сопвей Ь] ( яАггау<Т> гевц1Е(а.вххе() ); Хог(в1ке с К = О; К < а.в1зе() у ++К) ( // Сложение двух объектов ЯАггау Сещр1аее<сурепате Т> ЯАггау<Т> орегаеог+(ЯАггау<Т> сопвей а, ЯАггау<Т> сопвей Ь) ( Глава 18. Шаблоны выражений 350 геви1с[К] = а[К]*Ь[К] ) гесигп геви1г; // Умножение скаляра на объект ЯАггау Сетар1асе<гурепатпе Т> яАггау<Т> орегагог*(Т сопвсй в, ядггау<Т> сопела а) ( ЯАггау<Т> геви1Г(а.вйзе()); аког(вйзе с К = 0; К < а.вйхе(); ++К) ( геви1с(К] = в*а(К]; гегпгп геви1гз // Умножение объекта ЯАггау на скаляр; // сложение скаляра и объекта ЯАггау; // сложение объекта ЯАггау и скаляра Можно было бы написать еше много версий этих и других операторов, однако для нашего примера достаточно того, что есть.
// ехргсшр1/ваггау1.срр №1пс1пс)е "ваггау1.Ьрр" №1пс1иг)е "ваггауорв1.Ьрр" 1пг шайп() ( ЯАггау<г)опЬ1е> х(1000), у(1000); //... х = 1.2*х + х*у' ) Однако оказывается, что приведенная выше реализация операторов крайне неэффективна. Тому есть две причины. 1. При каждом применении оператора (за исключением оператора присвоения) создается, как минимум, один временный массив. Это означает, что в нашем примере создастся по крайней мере три временных массива, в каждом из которых сойеР' жится по 1000 элементов (это при условии, что самому компилятору не требуегс" никаких дополнительных временных копий). 2. При каждом применении оператора компилятору требуется выполнить дополни тельный обход массивов-аргументов и результирующих массивов. В нашем пр" мере это приводит к необходимости считать приблизительно б000 и записать око ло 4000 значений типа боиЬ1е.
18.1. Временные объекты и раздельные циклы 351 Оценим количество операций, которые выполняются в циклах, обрабатывающих временные массивы. из 1000 операций создание и удаление из 1000 операций создание и удаление из 1000 операций создание и удаление операций считывания операций записи Сгар1 = 1.2*х; сюр2 = х*у; СИЗ = Сюр1+Стр2; // Цикл // плюс // Цикл // плюс // Цикл // плюс // 1000 // 1000 массива стр1 массива сюр2 массива гюрЗ и х = сюрЗ // ехргсюр/ваггауорв2.)зрр // Присвоение с прибавлением объекта ЯАггау сеюр1аге<с1авв Т> ЯАггау<Т>а ЯАггау<Т>::орегасог += (ЯАггау<Т> сопвгй )э) ( бог(айке с К = 0; К < вйае(); ++К) ( (*Г)зйв) (К) += )э(К); ) гесигп *с)зйв/ // Присвоение с умножением объекта ЯАггау Сеюр1аге<с1авв Т> ЯАггау<Т>а ЯАггау<Т>::орегасог *= (ЯАггау<Т> сопвсй )Э) ( бог(в1ке с К = Ор К < айке() ю ++К) ( (*с)зйв) 1К) *= )э[К); ') Если в компиляторе не применяетсл специальный высокопроизводительный распределитель памяти, то при работе с небольшими массивами основное время затрачивается на создание ненужных временных массивов.
Для выполнения операций с очень большими массивами использование временных массивов абсолютно недопустимо, поскольку может оказаться, что их негде хранить. (Чтобы получить достоверные результаты при численном моделировании реальных систем, часто расходуется вся доступная память. Если же она будет применяться для хранения ненужных временных массивов, это приведет к снижению точности расчетов.) В ранних реализациях численных библиотек, предназначенных для работы с массивами, эта проблема оставалась нерешенной, и пользователям рекомендовалось вместо операторов сложения и умножения применять операторы присвоения со сложением и присвоения с умножением.
Преимушество последних состоит в том, что в них вызывающий массив является одновременно и аргументом, и целевым массивом, поэтому они не требуют временных массивов. Например, два объекта класса ЯАггау можно было бы сложить, как показано ниже. Глава 18. Шаблоны выражений 352 гегигп *гав; ) // Присвоение с умножением на скаляр Гезар1асе<с1авв Т> БАггау<т>а Бдггау<т>::орегагог *= (т сопвгй в) ( бог(в1ае г й = О) )с < вйае(); ++)с) ( (*гЬТв) [)с) *= в; ) гесигп *сЬТв; ) С помощью подобных операторов наш вычислительный пример можно было бы переписать.
// ехргсвр1/ваггау2.срр ()Тпс1иде "ваггау2.Ьрр" ()1пс1ис)е "ваггауорв1.Ьрр" ()Тпс1нс)е "ваггауорв2.Ьрр" Тпг тайп() ( БАггау<с)онЫе> х(1000), у(1000) //... // Вычисляем х = 1.2*х + х*у БАггау<доиЫе> агар(х)з агар *= у; х *= 1.2; х += сыр; Понятно, что метод, при котором используются только операторы присвоения с вычислением, тоже не оправдывает наших ожиданий.
Он обладает такими недостатками: ° обозначения становятся громоздкими; ° полностью избавиться от ненужных временных массивов не удается; ° в теле цикла выполняется множеспю операций; в результате для его выполнения тре- буется выполнить около 6000 считываний и 4000 записей значений типа с)онЫе. На самом деле нам нужен некий единый "идеальный" цикл, в котором бы все выражение вычислялось для каждого индекса. Тпс ва1п() ( БАггау<с)оиЫе> х(1000), у(1000) ю аког(йпс з.с)х = 0; Тс)х < х.в1хе() ю ++1<)х) ( 18.2. Программирование выражений в аргументах шаблонов 353 х[1с)х) = 1.2*х[1с)х] + х[1с)х]*у[йс)х); ) ) На этот раз удалось обойтись без временных массивов и ограничиться в каждой итерации цикла всего двумя операциями считывания (элементов массивов х[1с]х] и у [йс]х) ) и одной операцией записи в память (х [йс)х] ).
В результате для выполнения всего цикла требуется выполнить около 2000 считываний и 1000 записей в память. На современных высокопроизводительных компьютерах пропускная способность памяти является ограничивающим фактором, снижающим скорость выполнения операций с массивами. Поэтому неудивительно, если на практике окажется, что производительность запрограммированного "вручную" цикла на один-два порядка выше, чем производительность продемонстрированного только что подхода, при котором применяется такая простая перегрузка оператора Однако желательно добиться такой же производительности более элегантным путем, избегая непонятных обозначений нли громоздкого кода, в котором легко допустить ошибку.
18.2. Программирование выражений в аргументах шаблонов Чтобы добиться цели, сформулированной в конце предыдущего раздела, нужно попытаться вычислить выражение не по частям, а сразу (т.е. так, чтобы справа от оператора присвоения находилось все выражение). Таким образом, перед вычислением нужно записать, какая операция к какому объекту будет применяться. Эти операции определяются во время компиляции,' поэтому они могут быть запрограммированы как аргументы шаблонов. Для выражения 1. 2*х + х*у;, рассматриваемого в качестве примера,'это означает, что результат умножения 1. 2*х — это не новый массив, а обьект, в котором каждое значение х умножается на 1.2. Аналогично, в результате операции х*у получается объект, в котором каждый элемент массива х умножается на соответствующий элеменш массива у.
И наконец, когда нам понадобятся значения результирующего массива, выполняются все. упомянутые (и отложенные) действия. Рассмотрим конкретную реализацию сформулированной выше программы действий. В процессе этой реализации выражение 1. 2*х + х*у; преобразуется в объект такого типа: А Ас]б< А Мп1С<А Яса1ак<боиЫе>,Аггау<бопЫе», А Ми1К<Аккау<с]оиЫе>, Аггау<боиЫе» > В приведенных строках кода новый фундаментальный шаблон класса Аттау комбинируется с шаблонами классов А Яса1аг, и иЫ и А Ми1с. Сопоставьте расположение имен шаблонов с синтаксическим деревом, соответствующим рассматриваемому выражению (Рис. 18.1) В этом вложенном шаблоне представлены типы участвующих в вычислении объектов и оперы4ии нал ними.