|
Программирование >> Оптимизация возвращаемого значения
operator+ реализован через operator+=. Почему возвращаемое значение имеет атрибут const, объясняется в правиле б, см. также предупреждение о реализации на стр. 121. const Rational operatorf(const Rational& Ihs, const Rational& rhs) { return Rational(Ihs) += rhs; } operator- реализован через operator-=. -const Rational operator-(const Rational& Ihs, const Rational& rhs) { return Rational(Ihs) -= rhs; } В этом примере операторы += и -= реализованы с нуля в каком-то другом месте, а operator+ и operator- вызывают их, обеспечивая собственные функции. В таком сл5ае необходимо поддерживать только операторы присваивания. Более того, если операторы присваивания реализованы в открытом интерфейсе класса, то не нужно, чтобы отдельные операторы были дружественными к классу. Если нет причин, по которым все отдельные операторы нельзя сделать глобальными, то устранить необходимость написания отдельных операторов можно с помощью шаблонов: template<class Т> const Т operator+(const Т& Ihs, const Т& rhs) { return T(lhs) += rhs; См. комментарии выше. template<class Т> const Т operator-(const T& Ihs, const T& rhs) { return T(lhs) -= rhs; См. комментарии выше. Тогда, если для какого-либо типа Т определен оператор присваивания, то соответствующий отдельный оператор при необходимости будет генерироваться автоматически. Все это хорошо, но пока еще не рассматривались вопросы эффективности, которым посвящена настоящая глава. Здесь нужно обратить внимание на три момента. Во-первых, в общем случае операторы присваивания обычно более эффективны, чем соответствующие версии отдельных операторов, поскольку отдельные операторы, как правило, возвращают новый объект, и при этом также создается и уничтожается временный объект (см. правила 19 и 20). Операторы result += b result += с result += d He нужно создавать временный объект. Не нужно создавать временный объект. Не нужно создавать временный объект. Первый вариант проще написать, отлаживать и поддерживать, при этом в 80% случаев он обеспечивает приемлемую производительность (см. правило 16). Второй вариант более эффективен, и, возможно, более понятен для программистов на языке ассемблера. Включая оба варианта, вы позволяете пользователям разрабатывать и отлаживать код с помощью более простых отдельных операторов, сохраняя при этом возможность их замены более эффективными операторами присваивания. Более того, реализовав отдельные операторы через операторы присваивания, вы гарантируете, что при переходе от одного варианта к другому семантика операторов останется неизменной. И последнее замечание об эффективности касается реализации отдельных операторов. Снова рассмотрим реализацию operator*: template<class Т> const Т operator* (const Т& Ihs, const Т& rhs) { return T(lhs) *= rhs; } Выражение T (Ihs) - это вызов конструктора копирования объекта Т, создающего временный объект, значение которого равно Ihs. Затем пол5енный временный объект используется для вызова operator*= с параметром rhs, а результат операции возвращается operator* *. Кажется, что этот код слишком запутан. Может быть, лучше написать его так: По меньшей мере предполагается, что это так. Упы, некоторые компиляторы интерпретируют T(lhs) как приведение типа, снимающее атрибут const с Ihs, затем складывают rhs и Ihs и возвращают ссылку на измененный параметр Ihs! Поэтому прежде чем полагаться на описанное выше поведение, протестируйте ваш компилятор. же присваивания производят запись в аргумент слева, поэтому в данном cnjiae нет необходимости создавать временный объект для хранения возвращаемого значения оператора. Во-вторых, создавая и отдельные операторы, и соответствующие им операторы присваивания, вы позволяете клиентам ваших классов достигать компромисса между эффективностью и удобством. При этом пользователи могут выбирать, писать ли код так: Rational а, Ь, с, d, result; result =a+b+c+d; Возможно использует 3 временных объекта, по одному для каждого вызова operator*. ИЛИ так: result = а; Не нужно создавать временный объект. template<class Т> const Т operator+(const T& Ihs, const T& rhs) { T result(Ihs); Скопировать Ihs в результат, return result += rhs; Прибавить к нему rhs и вернуть } в качестве результата. Этот шаблон почти эквивалентен предыдущему, но между ними есть важное различие. Второй шаблон содержит именованный объект result. А это означает, что оптимизация возвращаемого значения (см. правило 20) для такой реализации operator + до недавнего времени была невозможной (см. сноску на стр. 119). Для первой же реализации оптимизация возвращаемого значения разрешалась всегда, поэтому существует больше шансов, что ваш компилятор сможет сгенерировать оптимизированный код. Но принципы честной рекламы вынуждают меня отметить также, что выражение return T(lhs) += rhs; многие компиляторы рассматривают как слишком сложное для оптимизации возвращаемого значения. Поэтому первая реализация может потребовать создания одного временного объекта, так же как и при использовании именованного объекта result. Тем не менее, неименованные объекты всегда было проще устранять, чем именованные, поэтому при выборе между именованным объектом и временным, возможно, будет лучше использовать второй из них. Это никогда не обойдется дороже, чем применение именованного объекта, а в некоторых компиляторах, особенно в старых, потребует даже меньше затрат. Все рассуждения об именованных и неименованных объектах и оптимизациях компиляторов, конечно же, небезынтересны, но не стоит забывать о главном. Операторы присваивания (такие как operator+=) обычно более эффективны, чем соответствующие отдельные операторы (например, operator+). При разработке библиотек следует включать в нее обе версии операторов, а для выигрыша в производительности по возможности использовать операторы присваивания вместо соответствующих отдельных операторов. Правило 23. Используйте разные библиотеки Разработка библиотеки - один из примеров компромисса. В идеале, библиотека должна быть компактной, быстрой, мощной, гибкой, расширяемой, интуитивно понятной, универсальной, легко поддерживаться, быть свободной от ограничений и безошибочной. Но таких библиотек не существует. Библиотеки, оптимизированные по скорости выполнения и размеру, обычно не переносимы на другие компьютеры. Библиотеки с богатыми функциональными возможностями редко бывают интуитивно понятными. Безошибочные библиотеки обладают ограниченными возможностями. В нашем мире нельзя получить все сразу: чем-то всегда приходится жертвовать. Разработчики присваивают каждому из этих критериев различные приоритеты, принося в жертву то одно, то другое. В результате зачастую две аналогичные
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |