Мегаобучалка Главная | О нас | Обратная связь


Сохраняйте естественную семантику перегруженных операторов



2016-01-26 389 Обсуждений (0)
Сохраняйте естественную семантику перегруженных операторов 0.00 из 5.00 0 оценок




 

 

Резюме

 

Программисты ненавидят сюрпризы. Перегружайте операторы только в случае веских на то оснований, и сохраняйте при этом их естественную семантику. Если это оказывается сложным, возможно, вы неверно используете перегрузку операторов.

 

Обсуждение

 

Хотя все (как мы надеемся) согласны с тем, что не следует реализовывать вычитание как оператор operator+, прочие ситуации не столь очевидны. Например, означает ли оператор operator* вашего класса Tensor скалярное или векторное умножение? Должен ли оператор operator+=(Tensor& t, unsigned u) прибавлять u к каждому из элементов t, или должен изменять размер t? В таких неоднозначных или не поддающихся интуитивному пониманию случаях следует использовать именованные функции, а не прибегать к шифрованию.

Для типов-значений (но не для всех типов; см. рекомендацию 32) следует придерживаться правила: "Если не знаешь, как поступить — поступай так, как int" [Meyers96]. Подражание поведению операторов встроенных типов и взаимоотношениям между ними гарантирует, что вы никого не приведете в замешательство. Если выбранная вами семантика заставляет кого-то удивленно поднять брови, может быть, перегрузка оператора — не самая лучшая идея?

Программисты ожидают, что операторы идут в связке — если выражение a@b имеет определенный смысл для некоторого определенного вами оператора @ (возможно, после преобразования типов), то задайте сами себе вопрос: можно ли написать b@a без неприятных последствий? Можно ли написать a@=b? (См. рекомендацию 27.) Если оператор имеет обратный оператор (например, + и -, * и /), то поддерживаются ли они оба?

От именованных функций не ожидается наличие соответствующих взаимоотношений, так что для большей ясности кода, если возможно неверное истолкование семантики перегруженных операторов, лучше использовать именно функции.

 

Исключения

 

Имеются высокоспециализированные библиотеки (например, генераторы синтаксических анализаторов), в предметной области которых соглашения о семантике операторов существенно отличаются от их значений в С++ (например, при работе с регулярными выражениями оператор operator* может использоваться для выражения "ноль или большее количество"). Предпочтительно найти альтернативу необычной перегрузке оператора (например, в регулярных выражениях [C++TR104] используются строки, так что * может использоваться естественным образом, без перегрузки операторов). Если все же после тщательных размышлений вы решили использовать операторы, убедитесь, что вы четко определили единую схему для всех ваших соглашений, и при этом не затеяли опасные игры со встроенными операторами.

 

Ссылки

 

[Cline99] §23.02-06 • [C++TR104] §7 • [Dewhurst03] §85-86 • [Koenig97] §4 • [Lakos96] §9.1.1 • [Meyers96] §6 • [Stroustrup00] §11.1 • [Sutter00] §41

 

Отдавайте предпочтение каноническим формам арифметических операторов и операторов присваивания

 

 

Резюме

 

Если можно записать а+b, то необходимо, чтобы можно было записать и a+=b. При определении бинарных арифметических операторов одновременно предоставляйте и их присваивающие версии, причем делайте это с минимальным дублированием и максимальной эффективностью.

 

Обсуждение

 

В общем случае для некоторого бинарного оператора @ (+, -, * и т.д.) вы должны также определить его присваивающую версию, так чтобы a@=b и a=a@b имели один и тот же смысл (причем первая версия может быть более эффективна). Канонический способ достижения данной цели состоит в определении @ посредством @= следующим образом:

T& T::operator@=(const T&) {

// ... реализация ...

return *this;

}

 

T operator@(const T& lhs, const T& rhs) {

T temp(lhs);

return temp @= rhs;

}

Эти две функции работают в тандеме. Версия оператора с присваиванием выполняет всю необходимую работу и возвращает свой левый параметр. Версия без присваивания создает временную переменную из левого аргумента, и модифицирует ее с использованием формы оператора с присваиванием, после чего возвращает эту временную переменную.

Обратите внимание, что здесь operator@ — функция-не член, так что она обладает желательным свойством возможности неявного преобразования как левого, так и правого параметра (см. рекомендацию 44). Например, если вы определите класс String, который имеет неявный конструктор, получающий аргумент типа char, то оператор operator+(const String&, const String&), который не является членом класса, позволяет осуществлять операции как типа char+String, так и String+char; функция-член String::operator+(const String&) позволяет использовать только операцию String+char. Реализация, основной целью которой является эффективность, может определить ряд перегрузок оператора operator@, не являющихся членами класса, чтобы избежать увеличения количества временных переменных в процессе преобразований типов (см. рекомендацию 29).

Также делайте не членом функцию operator@= везде, где это возможно (см. рекомендацию 44). В любом случае, все операторы, не являющиеся членами, должны быть помещены в то же пространство имен, что и класс T, так что они будут легко доступны для вызывающих функций при отсутствии каких-либо сюрпризов со стороны поиска имен (см. рекомендацию 57).

Как вариант можно предусмотреть оператор operator@, принимающий первый параметр по значению. Таким образом вы обеспечите неявное копирование компилятором, что обеспечит ему большую свободу действий по оптимизации:

T& operator@=(T& lhs, const T& rhs) {

// ... реализация ...

return lhs;

}

 

T operator@(T lhs, const T& rhs) { // lhs передано по значению

return lhs @= rhs;

}

Еще один вариант — оператор operator@, который возвращает const-значение. Эта методика имеет то преимущество, что при этом запрещается такой не имеющий смысла код, как a+b=c, но в этом случае мы теряем возможность применения потенциально полезных конструкций наподобие а = (b+c).replace(pos, n, d). А это весьма выразительный код, который в одной строчке выполняет конкатенацию строк b и с, заменяет некоторые символы и присваивает полученный результат переменной а.

 

Примеры

 

Пример. Реализация += для строк. При конкатенации строк полезно заранее знать длину, чтобы выделять память только один раз:

String& String::operator+=( const String& rhs ) {

// ... Реализация ...

return *this;

}

 

String operator+( const String& lhs, const String& rhs ) {

String temp; // изначально пуста

// выделение достаточного количества памяти

temp.Reserve(lhs.size() + rhs.size());

// Конкатенация строк и возврат

return (temp += lhs) += rhs;

}

 

Исключения

 

В некоторых случаях (например, оператор operator*= для комплексных чисел), оператор может изменять левый аргумент настолько существенно, что более выгодным может оказаться реализация оператора operator*= посредством оператора operator*, а не наоборот.

 

Ссылки

 

[Alexandrescu03a] • [Cline99] §23.06 • [Meyers96] §22 • [Sutter00] §20

 

28. Предпочитайте канонический вид ++ и --, и вызов префиксных операторов

 

 

Резюме

 

Особенность операторов инкремента и декремента состоит в том, что у них есть префиксная и постфиксная формы с немного отличающейся семантикой. Определяйте операторы operator++ и operator-- так, чтобы они подражали поведению своих встроенных двойников. Если только вам не требуется исходное значение — используйте префиксные версии операторов.

 

Обсуждение

 

Старая шутка гласит, что язык называется С++, а не ++С, потому что язык был улучшен (на что указывает инкремент), но многие продолжают использовать его как С (предыдущее значение до инкремента). К счастью, эту шутку можно считать устаревшей, но это отличная иллюстрация для понимания отличия между двумя формами операторов.

В случае ++ и -- постфиксные формы операторов возвращают исходное значение, в то время как префиксные формы возвращают новое значение. Лучше всего реализовывать постфиксный оператор с использованием префиксного. Вот канонический вид такого использования:

// ---- Префиксные операторы -----------------------

T& T::operator++() { // Префиксный вид:

// выполнение // - Выполнение

// инкремента // действий

return *this; // - return *this;

}

 

T& T::operator--() { // Префиксный вид:

// Выполнение // - Выполнение

// декремента // действий

return *this; // - return *this;

}

 

// ---- Постфиксные операторы ---------------------

T T::operator++(int) { // Постфиксный вид:

T old(*this); // - Запоминаем старое значение

++*this; // - Вызов префиксной версии

return old; // - Возврат старого значения

}

 

T T::operator--(int) { // Постфиксный вид:

T old(*this); // - Запоминаем старое значение

--*this; // - Вызов префиксной версии

return old; // - Возврат старого значения

}

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

 

Исключения

 

Шаблоны, используемые для научных вычислений (например, шаблоны для представления тензоров или матриц), к которым предъявляются жесткие требования по производительности. Для таких шаблонов часто приходится искать более эффективные способы реализации префиксных форм операторов.

 

Ссылки

 

[Cline99] §23.07-08 • [Dewhurst03] §87 • [Meyers96] §6 • [Stroustrup00] §19.3 • [Sutter00] §6, §20

 



2016-01-26 389 Обсуждений (0)
Сохраняйте естественную семантику перегруженных операторов 0.00 из 5.00 0 оценок









Обсуждение в статье: Сохраняйте естественную семантику перегруженных операторов

Обсуждений еще не было, будьте первым... ↓↓↓

Отправить сообщение

Популярное:
Организация как механизм и форма жизни коллектива: Организация не сможет достичь поставленных целей без соответствующей внутренней...
Как распознать напряжение: Говоря о мышечном напряжении, мы в первую очередь имеем в виду мускулы, прикрепленные к костям ...



©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (389)

Почему 1285321 студент выбрали МегаОбучалку...

Система поиска информации

Мобильная версия сайта

Удобная навигация

Нет шокирующей рекламы



(0.009 сек.)