Чтение онлайн

ЖАНРЫ

Стандарты программирования на С++. 101 правило и рекомендация

Александреску Андрей

Шрифт:

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

29. Используйте перегрузку, чтобы избежать неявного преобразования типов

Резюме

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

Обсуждение

Когда вы работаете в офисе и у вас заканчивается бумага, что вы делаете? Правильно — вы идете к копировальному аппарату и делаете несколько копий чистого листа бумаги.

Как бы глупо это ни звучало, но зачастую это именно то, что делает неявное преобразование типов: создание излишних временных объектов только для того, чтобы выполнить некоторые тривиальные операции над ними и тут же их выбросить (см. рекомендацию 40). В качестве примера можно рассмотреть сравнение строк:

Поделиться с друзьями: