Почему некоторые операторы могут быть перегружены только как функции-члены, другие как функции-друзья, а остальные - как обе?
Почему некоторые операторы могут быть перегружены только как функции-члены, другие как не-свободные функции, а остальные - как обе?
Что за этим стоит?
Как запомнить, какие операторы могут быть перегружены как (членские, бесплатные или оба)?
4 ответа
В вопросе перечислены три класса операторов. Я думаю, что их объединение в список помогает понять, почему некоторые операторы ограничены в том, где они могут быть перегружены:
Операторы, которые должны быть перегружены как члены. Это довольно мало:
- Назначение
operator=()
, Разрешение назначений, не являющихся членами, кажется, открывает двери для операторов, угоняющих назначения, например, путем перегрузки для различных версийconst
квалификации. Учитывая, что операторы присваивания являются довольно фундаментальными, что представляется нежелательным. - Вызов функции
operator()()
, Правила вызова функций и перегрузки достаточно сложны, как есть. Кажется неуместным дополнительно усложнять правила, допуская операторы вызова функций, не являющиеся членами. - Индекс
operator[]()
, При использовании интересных типов индексов кажется, что это может помешать доступу к операторам. Несмотря на то, что существует небольшая опасность перехвата перегрузок, кажется, что нет большого выигрыша, но есть интересный потенциал для написания крайне неочевидного кода. - Доступ к классу
operator->()
, Вне руки я не вижу каких-либо недобросовестных злоупотреблений перегрузкой этого оператора, не являющегося участником. С другой стороны, я тоже ничего не вижу. Кроме того, оператор доступа к членам класса имеет довольно специальные правила, и игра с потенциальными перегрузками, мешающими этим, кажется ненужным осложнением.
Хотя возможно перегрузить каждый из этих членов, не являющихся членами (особенно оператор нижнего индекса, который работает с массивами / указателями, и они могут быть на любой стороне вызова), кажется удивительным, если, например, присвоение может быть перехвачено перегрузкой не-члена, которая лучше соответствует одному из назначений-членов. Эти операторы также довольно асимметричны: как правило, вы не захотите поддерживать преобразование с обеих сторон выражения, включающего эти операторы.
Тем не менее, например, для библиотеки лямбда-выражений было бы неплохо, если бы была возможность перегрузить все эти операторы, и я не думаю, что есть внутренняя техническая причина, препятствующая перегрузке этих операторов.
- Назначение
Операторы, которые должны быть перегружены как функции, не являющиеся членами.
- Пользовательский литерал
operator"" name()
Этот оператор в некотором роде странный шар и, возможно, на самом деле не совсем оператор. В любом случае, нет объекта для вызова этого члена, для которого могут быть определены члены: левый аргумент пользовательских литералов всегда является встроенным типом.
- Пользовательский литерал
Не упоминается в вопросе, но есть оператор, который вообще не может быть перегружен:
- Выбор участника
.
- Оператор доступа к объекту указатель на член
.*
- Оператор области
::
- Троичный оператор
?:
Считалось, что эти четыре оператора слишком фундаментальны, чтобы на них вообще можно было вмешиваться. Хотя было предложение разрешить перегрузку
operator.()
в какой-то момент нет сильной поддержки в этом (основной вариант использования - это умные ссылки). Хотя, конечно, есть некоторые контексты, которые можно было бы перегрузить и для этих операторов.- Выбор участника
Операторы, которые могут быть перегружены либо как члены, либо как не члены. Это основная часть операторов:
- Пре- и постинкрементный -декремент
operator++()
,operator--()
,operator++(int)
,operator--(int)
- [Одинарный] разыменование
operator*()
- [Унарный] адрес
operator&()
- [Одинарные] знаки
operator+()
,operator-()
- Логическое отрицание
operator!()
(или жеoperator not()
) - Побитовая инверсия
operator~()
(или жеoperator compl()
) - Сравнения
operator==()
,operator!=()
,operator<()
,operator>()
,operator<=()
, а такжеoperator>()
- [Двоичная] арифметика
operator+()
,operator-()
,operator*()
,operator/()
,operator%()
- [Двоичный] побитовый
operator&()
(или жеoperator bitand()
),operator|()
(или жеoperator bit_or()
),operator^()
(или жеoperator xor()
) - Побитовый сдвиг
operator<<()
а такжеoperator>>()
- Логика
operator||()
(или жеoperator or()
) а такжеoperator&&()
(или жеoperator and()
) - Операция / назначение
operator@=()
(за@
быть подходящим символом оператора () - Последовательность
operator,()
(для которого перегрузка фактически убивает свойство sequence!) - Указатель указатель на член доступа
operator->*()
- Управление памятью
operator new()
,operator new[]()
,operator new[]()
, а такжеoperator delete[]()
Операторы, которые могут быть перегружены либо как члены, либо как не члены, не так необходимы для обслуживания основного объекта, как другие операторы. Это не значит, что они не важны. На самом деле, этот список содержит несколько операторов, где довольно сомнительно, должны ли они быть перегруженными (например, адрес
operator&()
или операторы, которые обычно вызывают последовательность, т. е.operator,()
,operator||()
, а такжеoperator&&()
,- Пре- и постинкрементный -декремент
Конечно, стандарт C++ не дает обоснования того, почему все делается так, как они (а также нет записей о первых днях, когда эти решения были приняты). Лучшее обоснование, вероятно, можно найти в "Дизайне и эволюции C++" Бьярна Страуструпа. Напоминаю, что там обсуждались операторы, но электронной версии, похоже, нет.
В целом, я не думаю, что есть действительно веские причины для ограничений, кроме потенциального осложнения, которое в большинстве случаев не считалось стоящим усилий. Я, однако, сомневаюсь, что ограничения, вероятно, будут сняты, поскольку взаимодействие с существующим программным обеспечением неизбежно изменит смысл какой-либо программы непредсказуемым образом.
Обоснование состоит в том, что для них не имеет смысла быть не членами, так как вещь в левой части оператора должна быть экземпляром класса.
Например, принимая класс A
A a1;
..
a1 = 42;
Последнее утверждение на самом деле является следующим вызовом:
a1.operator=(42);
Это не имело бы смысла для вещи на LHS . не должен быть экземпляром A, и поэтому функция должна быть членом.
Потому что вы не можете изменить семантику примитивных типов. Не имеет смысла определять, как operator=
работает на int
, как задержать указатель, или как работает доступ к массиву.
Вот один пример: когда вы перегружаете << operator
для class T
подпись будет:
std::ostream operator<<(std::ostream& os, T& objT )
где реализация должна быть
{
//write objT to the os
return os;
}
Для <<
оператор первый аргумент должен быть объектом o stream, а второй аргумент ваш объект класса T.
Если вы попытаетесь определить operator<<
в качестве функции-члена вы не сможете определить ее как std::ostream operator<<(std::ostream& os, T& objT)
, Это связано с тем, что функции-члены бинарных операторов могут принимать только один аргумент, а вызывающий объект неявно передается в качестве первого аргумента, используя this
,
Если вы используете std::ostream operator<<(std::ostream& os)
подпись как функция-член, вы на самом деле в конечном итоге с функцией-членом std::ostream operator<<(this, std::ostream& os)
который не будет делать то, что вы хотите. Поэтому вам нужен оператор, который не является функцией-членом и может получить доступ к данным-членам (если в вашем классе T есть личные данные, которые вы хотите передать, operator<<
должен быть другом класса T).