Почему некоторые операторы могут быть перегружены только как функции-члены, другие - как функции друзей, а остальные - как оба?

Почему некоторые операторы могут быть перегружены только как функции-члены, другие - как "свободные" функции, не являющиеся членами, а остальные - как?

В чем причина этого?

Как помнить, какие операторы могут быть перегружены как что (член, свободный или оба)?

Ответ 1

В вопросе перечисляются три класса операторов. Думаю, объединение их в список помогает понять, почему несколько операторов ограничены тем, где они могут быть перегружены:

  • Операторы, которые должны быть перегружены как члены. Их довольно мало:

    • Назначение 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())
    • Операция/назначение [email protected]=() (для @ является подходящим символом оператора()
    • Последовательность operator,() (для которой перегрузка фактически убивает свойство последовательности!)
    • Указатель указателя на элемент operator->*()
    • Управление памятью operator new(), operator new[](), operator new[]() и operator delete[]()

    Операторы, которые могут быть перегружены либо как члены, либо как нечлены, не являются необходимыми для обслуживания основных объектов, как и другие операторы. Это не значит, что они не важны. Фактически, этот список содержит несколько операторов, где довольно сомнительно, должны ли они быть перегружаемыми (например, адрес operator&() или операторы, которые обычно вызывают последовательность, т.е. operator,(), operator||() и operator&&().

Конечно, стандарт С++ не дает оснований для объяснения того, почему все делается так, как это делается (а также нет записей о ранних днях, когда эти решения принимаются). Лучшее обоснование, вероятно, можно найти в "Design and Evolution of С++" Бьярне Страуструпа. Я помню, что операторы обсуждались там, но, похоже, нет доступной электронной версии.

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

Ответ 2

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

Например, если предположить, что класс A

A a1;
..
a1 = 42;

Последний оператор - это действительно такой вызов:

a1.operator=(42);

Не было бы смысла, если бы предмет LHS . не был экземпляром A, поэтому функция должна быть членом.

Ответ 3

Потому что вы не можете изменить семантику примитивных типов. Не имеет смысла определять, как operator= работает на int, как относиться к указателю или как работает доступ к массиву.

Ответ 4

Вот один пример: Когда вы перегружаете << operator для class T, подпись будет:

std::ostream operator<<(std::ostream& os, T& objT )

где реализация должна быть

{
//write objT to the os
return os;
}

Для оператора << первым аргументом должен быть объект ostream, а второй - объект класса 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).