|
Программирование >> Оптимизация возвращаемого значения
Как обычно, вам не нужны лишние затраты. Чтобы избежать их, в рассматриваемом случае вы можете использовать похожую функцию, operator+=; это преобразование описано в правиле 22. Но для большинства функций, выдающих объекты, не существует аналогов, то есть в принципе, концептуально, нельзя избежать создания и удаления возвращаемого значения. Однако между концепцией и действительностью лежит сумеречная зона , называемая оптимизацией, и иногда вы можете записывать функции, возвращающие объекты, так, что это позволит вам не создавать временные объекты. Наиболее часто применяется оптимизация возвращаемого значения, которая обсуждается в правиле 20. Подводя итог, можно сказать, что создание временных объектов - довольно дорогое удовольствие, поэтому постарайтесь его избегать. Важнее научиться находить места, где могут создаваться временные объекты. Когда вы видите параметр типа ссылка на const , помните, что допускается создание временного объекта. Когда вы видите функцию, возвращающую объект, знайте, что будет создан (а затем удален) временный объект. Учитесь находить такие конструкции, и вы лучше поймете, какие ресурсы уходят на выполнение закулисных действий компилятора. Правило 20. Облегчайте оптимизацию возвра1цаемого значения Функция, которая возвращает объекты, не по вкусу ревностным по1слонникам эффективности, потому что возврат по значению, включая подразумеваемые вызовы конструктора и деструктора (см. правило 19), не могут быть устранены. Проблема проста: функция либо должна вернуть объект, чтобы выполнить работу, либо нет. Если она возвращает объект, то нет никакого способа избавиться от этого. Рассмотрим функцию operator* для действительных чисел: class Rational { public: Rational (int numerator = 0, int denominator = 1) ; int numerator{) const; int denominator() const ; }; Причины постоянства (const) вызываемого значения указаны в правиле 6. const Rational operator*(const Rational& Ihs, const Rational& rhs); Даже не глядя на код функции operator*, вы знаете, что она должна вернуть объект, потому что возвращает произведение двух произвольных чисел. Как функция operator* может избежать создания нового объекта для хранения их произведения? Никак, поэтому она должна создавать новый объект и возвращать его. Тем не менее, программисты С++ приложили усилия, достойные Геркулеса, чтобы найти способ избавиться от возврата результатов по значению. Некоторые программисты возвращают указатели, что приводит к синтаксической пародии: Неблагоразумный способ избежать возвращения объекта, const Rational * operator*(const Rational& Ihs, const nationals rhs); Rational a = 10; Rational b(l, 2); Rational с = * (a * b) ; По-вашему, это естественно? В связи с этим возникает вопрос. Должна ли вызывающая программа удалять указатель, возвращенный функцией? Обычно отвечают да , но это, как правило, ведет к утечке ресурсов. Другие разработчики возвращают ссылки, что дает в общем-то приемлемый синтаксис: Опасный (и неправильный) способ избежать возвращения объекта, const nationals operator*(const Rationals Ihs, const Rationals rhs) ; Rational a = 10; Rational b(l, 2); Rational с = a * b; Выглядит вполне благоразумно. Но поведение таких функций нельзя реализовать корректно. Обычная попытка выглядит примерно так: Еще один опасный (и неправильный) способ избежать возвращения объекта, const Rationals operator*(const Rationals Ihs, const Rationals rhs) { Rational result(Ihs.numerator() * rhs.numerator(), Ihs.denominator() * rhs.denominator()); return result; } Эта функция возвращает ссылку на объект, который больше не существует. В частности, она возвращает ссылку на локальный объект result, но result был автоматически удален при выходе из operator*. Возвращение ссылки на объект, который был уничтожен, вряд ли полезно. Поверьте, некоторые функции (operator* в том числе) все равно будут возвращать объекты. Не стоит бороться с тем, чего вы не сможете победить. Вы не получите желаемого результата, пытаясь устранить возвращение объектов по значению функциями, которые требуют этого. В принципе, вас и не должно волновать то, что функция возвращает объект, вам необходимо заботиться только о затратах, которые приносит возврат объекта. Поэтому главное - найти способ сократить затраты на возврат объектов, а не устранять сами объекты. Если создание таких объектов не влечет никаких затрат, то не все ли равно, сколько их будет? В июле 1996 комитет по стандартизации ISO/ANSI объявил, что как именованные, так и неименованные объекты могут быть оптимизированы при помощи оптимизации возвращаемого значения, так что обе версии operator* (см. выше) могут теперь выдавать один и тот же (оптимизированный) объектный код. Очень часто функции, которые возвращают объекты, используются таким образом, что компиляторы могут устранить затраты на создание временных объектов. Тонкость заключается в том, чтобы возвращать аргументы конструктора вместо объектов: эффективный и правильный способ реализации функции, которая возвращает объект. const Rational operator*(const Rational& Ihs, const Rational& rhs) return Rational(Ihs.numerator 0 * rhs.numerator() , Ihs.denominator() * rhs.denominator()) ; Взгляните внимательнее на возвращаемое выражение. Оно выглядит как вызов конструктора Rational, и фактически так оно и есть. Вы создаете временный объект Rational при помощи выражения: Rational(Ihs.numerator о * rhs.numerator() , Ihs.denominator () * rhs.denominator ()) ; Именно этот временный объект и копируется функцией в качестве возвращаемого ею значения. Кажется, что возврат аргументов конструктора вместо локальных объектов не принесет вам много пользы, потому что вы все еще должны покрывать затраты на создание и удаление временного объекта, появляющегося внутри функции, и вы все еще должны покрывать затраты на создание и удаление объекта, который возвращается функцией. Но вы уже получили кое-что. Правила языка C-i-i- позволяют компиляторам выполнять оптимизацию за счет удаления временных объектов. В результате, если вы вызываете operator* в следующем контексте: Rational а = 10 ; Rational b(l, 2) ; Rational с = а * b; operator* вызывается здесь. то ваши компиляторы могут устранять как временный объект внутри operator*, так и временный объект, возвращаемый operator*. Они могут создавать объект, определенный выражением return в памяти, выделенной под объект с. Если ваши компиляторы делают это, то общие затраты на образование временных объектов в результате вызова operator* сводятся к нулю. Чтобы создать объект с, выполняется только один вызов конструктора. Кроме того, нельзя предложить ничего лучшего, потому что с - именованный объект, а именованные объекты не могут быть удалены (см. также правило 22)*. Однако вы можете
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |