2009-07-15 6 views
43

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

В чем причина этих проблем?

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

+5

@BROY Ваши изменения неверны, функция _non-member_ необязательно является _friend_. (И я также обнаружил, что ваше изменение изменило [много] (http://stackoverflow.com/posts/1132600/revisions) на исходный вопрос.) –

ответ

7

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

Например, предположим, класса А

A a1; 
.. 
a1 = 42; 

Последнее утверждение действительно вызов, как это:

a1.operator=(42); 

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

+2

Я могу думать о пользе. Например, класс B теоретически может изменить, как он назначен A, перегружая operator = (A &, B), но B по какой-то причине не хочет определять оператор трансляции A (например, потому что вы не хотите другие неявные отбрасывания). Это желание может быть неразумным, против обычной практики и т. Д., Но я не уверен, что это бессмысленно или что вы (пока) сделали это против него. –

+0

Что ж, на самом деле это не имеет значения, если я не против этого, мы должны принять то, что говорит стандарт. И, конечно же, вы можете делать (почти) все, что вам нравится, через функцию имени друга. –

+1

Имеет смысл запретить такие операции для примитивных типов, но почему бы не разрешить глобальный * operator [] (const MyClass &, int) * и make * operator [] (void *, int) * вызвать ошибку, в частности, из-за примитива тип? –

5

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

0

Вот один пример: При перегрузке << 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).

28

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

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

    1. Назначение operator=(). Разрешение присвоений, не являющихся членами, похоже, открывает дверь для захвата операторами операторов, например., путем перегрузки для разных версий квалификации const. Учитывая, что операторы присваивания являются довольно фундаментальными, что представляется нежелательным.
    2. Функция вызова operator()(). Правила вызова функции и перегрузки достаточно сложны, как есть. Представляется нецелесообразным дополнительно усложнять правила, разрешая операторам-операторам, не являющимся членами.
    3. Индекс operator[](). Использование интересных типов индексов, похоже, может помешать доступу к операторам. Хотя существует небольшая опасность перехвата перегрузок, похоже, нет большого выигрыша, но интересный потенциал для написания очень неочевидного кода.
    4. Член класс доступ operator->(). Вне рук я не вижу плохого злоупотребления перегрузкой этого оператора, не являющегося членом. С другой стороны, я тоже не вижу. Кроме того, оператор доступа к члену класса имеет довольно специальные правила, и играть с потенциальными перегрузками, мешающими им, кажется ненужным осложнением.

    Хотя можно предположить, перегружать каждый из этих элементов являются не являющиеся членами (особенно индекс оператора, который работает на массивы/указатели и они могут быть по обе стороны вызова), кажется удивительным, если, например, , присваивание может быть захвачено перегрузкой, отличной от члена, которая лучше соответствует одному из назначений членов. Эти операторы также довольно асимметричны: вы, как правило, не хотели бы поддерживать преобразование с обеих сторон выражения, включающего эти операторы.

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

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

    1. Определенного пользователь буквальный operator"" name()

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

  3. Не упомянуто в вопросе, но есть также оператор, который не может быть перегружен на всех:

    1. Селектор член .
    2. Оператор доступа к объектам указатель на член .*
    3. Оператор сферы применения ::
    4. Терминальный оператор ?:

    Эти четыре оператора считались слишком фундаментальными, чтобы вообще вмешиваться. Хотя было предложение разрешить перегрузку operator.(), в какой-то момент не существует сильной поддержки (основной вариант использования - это умные ссылки). Хотя, безусловно, есть некоторые контексты, которые можно себе представить, где было бы неплохо перегрузить эти операторы.

  4. Операторы, которые могут быть перегружены либо в качестве членов, либо не являются членами.Это основная часть операторов:

    1. пред- и пост-инкремент/-decrement operator++(), operator--(), operator++(int), operator--(int)
    2. код [Унарный] разыменовать operator*()
    3. код [Унарный] адрес- operator&()
    4. код [унарный] признаки operator+(), operator-()
    5. логическое отрицание operator!() (или operator not())
    6. Поразрядные инверсии operator~() (или operator compl())
    7. Сравнения operator==(), operator!=(), operator<(), operator>(), operator<=() и operator>()
    8. кода [двоичный] арифметическое operator+(), operator-(), operator*(), operator/(), operator%()
    9. кода [двоичного ] побитовое operator&() (или operator bitand()), operator|() (или operator bit_or()), operator^() (или operator xor())
    10. Поразрядного сдвиг operator<<() и operator>>()
    11. Логики operator||() (или operator or()) и operator&&() (или operator and())
    12. Операции/назначение [email protected]=() (для @ является символом подходящего оператора()
    13. Последовательности operator,() (для которых перегрузка фактически убивает свойство последовательности!)
    14. Доступный указатель на указатель на объект operator->*()
    15. Память Управление operator new(), operator new[](), operator new[]() и operator delete[]()

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

Конечно, стандарт C++ не дает оснований полагать, почему все сделано так, как они сделаны (а также нет записей о ранних днях, когда эти решения были приняты). Лучшее обоснование, вероятно, можно найти в " Дизайн и эволюция C++ »Бьярна Страуструпа. Напомню, что здесь обсуждались операторы, но, похоже, нет доступной электронной версии.

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

 Смежные вопросы

  • Нет связанных вопросов^_^