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

ЖАНРЫ

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

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

Шрифт:

• Всегда описывайте все указатели или ссылки на входные параметры как

const
.

• Предпочитайте передачу примитивных типов (например,

char
или
float
) и объектов-значений с недорогими операциями копирования (например,
Point
или
Complex<float>
) по значению.

• Входные аргументы прочих пользовательских типов лучше передавать как ссылки на

const
.

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

Далее

приведены рекомендации для выходных параметров (а также параметров для одновременного ввода и вывода информации).

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

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

Не используйте функции с переменным количеством аргументов (см. рекомендацию 98).

Ссылки

[Alexandrescu03a] • [Cline99] §2.10-11, 14.02-12, 32.08 • [Dewhurst03] §57 • [Koenig97] §4 • [Lakos96] §9.1.11-12 • [McConnell93] §5.7 • [Meyers97] §21-22 • [Stroustrup94] §11.4.4 • [Stroustrup00] §5.5, §11.6, §16.3.4 • [Sutter00] §6, §46

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

Резюме

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

Обсуждение

Хотя все (как мы надеемся) согласны с тем, что не следует реализовывать вычитание как оператор

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

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

Резюме

Если можно записать

а+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@
, принимающий первый параметр по значению. Таким образом вы обеспечите неявное копирование компилятором, что обеспечит ему большую свободу действий по оптимизации:

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