Есть много постов с жалобами на перегрузку операторов.
Я чувствовал, что должен прояснить концепции «перегрузки операторов», предлагая альтернативную точку зрения на эту концепцию.
Код запутывает?
Этот аргумент ошибочен.
Запутывание возможно на всех языках ...
Обфусцировать код в C или Java с помощью функций / методов так же легко, как и в C ++ с помощью перегрузок операторов:
// C++
T operator + (const T & a, const T & b) // add ?
{
T c ;
c.value = a.value - b.value ; // subtract !!!
return c ;
}
// Java
static T add (T a, T b) // add ?
{
T c = new T() ;
c.value = a.value - b.value ; // subtract !!!
return c ;
}
/* C */
T add (T a, T b) /* add ? */
{
T c ;
c.value = a.value - b.value ; /* subtract !!! */
return c ;
}
... Даже в стандартных интерфейсах Java
В качестве другого примера давайте рассмотрим интерфейс Cloneable
в Java:
Вы должны клонировать объект, реализующий этот интерфейс. Но ты мог бы лгать. И создать другой объект. На самом деле, этот интерфейс настолько слаб, что вы можете вернуть другой тип объекта, просто для удовольствия:
class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
}
Так как интерфейс Cloneable
может быть злоупотреблен / запутан, должен ли он быть запрещен по тем же причинам, что и перегрузка оператора C ++?
Мы могли бы перегрузить метод toString()
класса MyComplexNumber
, чтобы он возвращал строковый час дня. Следует ли запретить перегрузку toString()
? Мы можем саботировать MyComplexNumber.equals
, чтобы он возвращал случайное значение, изменял операнды ... и т. Д. И т. Д.
В Java, как в C ++ или на любом другом языке, программист должен соблюдать минимум семантики при написании кода. Это означает реализацию функции add
, которая добавляет, и метода реализации Cloneable
, который клонирует, и оператора ++
, который увеличивает значение.
Что в любом случае запутывает?
Теперь, когда мы знаем, что код можно саботировать даже с помощью нетронутых методов Java, мы можем задаться вопросом о реальном использовании перегрузки операторов в C ++?
Четкая и естественная запись: методы против перегрузки операторов?
Ниже мы сравним, для разных случаев, «один и тот же» код в Java и C ++, чтобы понять, какой стиль кодирования более понятен.
Естественные сравнения:
// C++ comparison for built-ins and user-defined types
bool isEqual = A == B ;
bool isNotEqual = A != B ;
bool isLesser = A < B ;
bool isLesserOrEqual = A <= B ;
// Java comparison for user-defined types
boolean isEqual = A.equals(B) ;
boolean isNotEqual = ! A.equals(B) ;
boolean isLesser = A.comparesTo(B) < 0 ;
boolean isLesserOrEqual = A.comparesTo(B) <= 0 ;
Обратите внимание, что A и B могут быть любого типа в C ++, если предусмотрены перегрузки операторов. В Java, когда A и B не являются примитивами, код может стать очень запутанным, даже для примитивных объектов (BigInteger и т. Д.) ...
Природные массивы / контейнерные методы доступа и подписка:
// C++ container accessors, more natural
value = myArray[25] ; // subscript operator
value = myVector[25] ; // subscript operator
value = myString[25] ; // subscript operator
value = myMap["25"] ; // subscript operator
myArray[25] = value ; // subscript operator
myVector[25] = value ; // subscript operator
myString[25] = value ; // subscript operator
myMap["25"] = value ; // subscript operator
// Java container accessors, each one has its special notation
value = myArray[25] ; // subscript operator
value = myVector.get(25) ; // method get
value = myString.charAt(25) ; // method charAt
value = myMap.get("25") ; // method get
myArray[25] = value ; // subscript operator
myVector.set(25, value) ; // method set
myMap.put("25", value) ; // method put
В Java мы видим, что для каждого контейнера, выполняющего одно и то же (доступ к его содержимому через индекс или идентификатор), у нас есть другой способ сделать это, что сбивает с толку.
В C ++ каждый контейнер использует один и тот же способ доступа к своему контенту благодаря перегрузке операторов.
Естественные сложные типы манипуляций
В приведенных ниже примерах используется объект Matrix
, найденный с помощью первых ссылок, найденных в Google для " Java Matrix object " и " c ++ Matrix object ":
// C++ YMatrix matrix implementation on CodeProject
// http://www.codeproject.com/KB/architecture/ymatrix.aspx
// A, B, C, D, E, F are Matrix objects;
E = A * (B / 2) ;
E += (A - B) * (C + D) ;
F = E ; // deep copy of the matrix
// Java JAMA matrix implementation (seriously...)
// http://math.nist.gov/javanumerics/jama/doc/
// A, B, C, D, E, F are Matrix objects;
E = A.times(B.times(0.5)) ;
E.plusEquals(A.minus(B).times(C.plus(D))) ;
F = E.copy() ; // deep copy of the matrix
И это не ограничивается матрицами. Классы BigInteger
и BigDecimal
Java страдают от той же запутанной многословности, в то время как их эквиваленты в C ++ столь же ясны, как и встроенные типы.
Естественные итераторы:
// C++ Random Access iterators
++it ; // move to the next item
--it ; // move to the previous item
it += 5 ; // move to the next 5th item (random access)
value = *it ; // gets the value of the current item
*it = 3.1415 ; // sets the value 3.1415 to the current item
(*it).foo() ; // call method foo() of the current item
// Java ListIterator<E> "bi-directional" iterators
value = it.next() ; // move to the next item & return the value
value = it.previous() ; // move to the previous item & return the value
it.set(3.1415) ; // sets the value 3.1415 to the current item
Природные функторы:
// C++ Functors
myFunctorObject("Hello World", 42) ;
// Java Functors ???
myFunctorObject.execute("Hello World", 42) ;
Объединение текста:
// C++ stream handling (with the << operator)
stringStream << "Hello " << 25 << " World" ;
fileStream << "Hello " << 25 << " World" ;
outputStream << "Hello " << 25 << " World" ;
networkStream << "Hello " << 25 << " World" ;
anythingThatOverloadsShiftOperator << "Hello " << 25 << " World" ;
// Java concatenation
myStringBuffer.append("Hello ").append(25).append(" World") ;
Хорошо, в Java вы тоже можете использовать MyString = "Hello " + 25 + " World" ;
... Но, подождите секунду: это перегрузка операторов, не так ли? Разве это не мошенничество ???
: - D
Общий код?
Одни и те же операнды, модифицирующие общий код, должны использоваться как для встроенных модулей / примитивов (которые не имеют интерфейсов в Java), так и для стандартных объектов (которые не могут иметь правильный интерфейс), и пользовательских объектов.
Например, вычисление среднего значения двух значений произвольных типов:
// C++ primitive/advanced types
template<typename T>
T getAverage(const T & p_lhs, const T & p_rhs)
{
return (p_lhs + p_rhs) / 2 ;
}
int intValue = getAverage(25, 42) ;
double doubleValue = getAverage(25.25, 42.42) ;
complex complexValue = getAverage(cA, cB) ; // cA, cB are complex
Matrix matrixValue = getAverage(mA, mB) ; // mA, mB are Matrix
// Java primitive/advanced types
// It won't really work in Java, even with generics. Sorry.
Обсуждение перегрузки оператора
Теперь, когда мы увидели справедливые сравнения между кодом C ++, использующим перегрузку операторов, и тем же кодом в Java, мы можем теперь обсудить «перегрузку операторов» как концепцию.
Перегрузка оператора существовала еще до появления компьютеров
Даже за пределами компьютерной науки существует перегрузка операторов: например, в математике такие операторы, как +
, -
, *
и т. Д. Перегружены.
Действительно, значение +
, -
, *
и т. Д. Изменяется в зависимости от типов операндов (чисел, векторов, квантовых волновых функций, матриц и т. Д.).
Большинство из нас, как часть наших научных курсов, изучили множество значений для операторов, в зависимости от типов операндов. Мы нашли их смущающими, их?
Перегрузка оператора зависит от его операндов
Это самая важная часть перегрузки операторов: как и в математике или физике, операция зависит от типов ее операндов.
Итак, знайте тип операнда, и вы узнаете эффект операции.
Даже C и Java имеют (жестко запрограммированную) перегрузку операторов
В С реальное поведение оператора будет меняться в зависимости от его операндов. Например, добавление двух целых чисел отличается от добавления двух двойных или даже одного целого и одного двойного. Существует даже целая арифметическая область указателя (без приведения можно добавить к указателю целое число, но нельзя добавить два указателя ...).
В Java нет арифметики указателей, но кто-то все же нашел, что конкатенация строк без оператора +
была бы достаточно нелепой, чтобы оправдать исключение в кредо "перегрузка оператора - зло".
Просто вы, как кодер C (по историческим причинам) или Java (по личным причинам , см. Ниже), не можете предоставить свой собственный.
В C ++ перегрузка операторов не является обязательной ...
В C ++ перегрузка операторов для встроенных типов невозможна (и это хорошо), но определяемые пользователем типы могут иметь определяемые пользователем перегрузки операторов .
Как уже говорилось ранее, в C ++ и, в отличие от Java, пользовательские типы не считаются гражданами второго сорта по сравнению со встроенными типами. Поэтому, если встроенные типы имеют операторы, пользовательские типы также должны иметь их.
Правда состоит в том, что, подобно методам toString()
, clone()
, equals()
для Java (, то есть квазистационарно ), перегрузка операторов C ++ является такой большой частью C ++, что оно становится таким же естественным, как исходные операторы C или ранее упомянутые методы Java.
В сочетании с шаблонным программированием перегрузка оператора становится широко известным шаблоном проектирования. На самом деле, вы не можете пойти очень далеко в STL, не используя перегруженные операторы и перегружая операторы для своего собственного класса.
... но этим не следует злоупотреблять
Перегрузка оператора должна стремиться соблюдать семантику оператора. Не вычитать в операторе +
(как в «не вычитать в add
функции» или «возвращать дерьмо в методе clone
»).
Перегрузка приведения может быть очень опасной, поскольку может привести к неясностям. Таким образом, они действительно должны быть зарезервированы для четко определенных случаев. Что касается &&
и ||
, никогда не перегружайте их, если вы действительно не знаете, что делаете, так как вы потеряете оценку короткого замыкания, которой обладают нативные операторы &&
и ||
.
Итак ... Хорошо ... Тогда почему это невозможно в Java?
Потому что Джеймс Гослинг сказал так:
Я исключил перегрузку операторов как довольно личный выбор , потому что я видел слишком много людей, злоупотребляющих им в C ++.
Джеймс Гослинг. Источник: http://www.gotw.ca/publications/c_family_interview.htm
Пожалуйста, сравните текст Гослинга выше с текстом Страуструпа:
Многие дизайнерские решения C ++ коренятся в моей неприязни к тому, чтобы заставлять людей делать что-то определенным образом [...] Часто у меня возникало желание запретить функцию, которая мне лично не нравилась, я воздерживался от этого, потому что Я не думал, что имею право навязывать свои взгляды другим .
Бьярне Страуструп. Источник: Разработка и развитие C ++ (1.3 Общая информация)
Будет ли перегрузка операторов полезной для Java?
Некоторые объекты могут значительно выиграть от перегрузки операторов (конкретные или числовые типы, такие как BigDecimal, комплексные числа, матрицы, контейнеры, итераторы, компараторы, парсеры и т. Д.).
В C ++ вы можете воспользоваться этим преимуществом благодаря скромности Страуструпа. В Java вы просто облажались из-за личного выбора Гослинга .
Можно ли добавить его в Java?
Причинами, по которым теперь не добавляется перегрузка операторов в Java, может быть сочетание внутренней политики, аллергии на эту функцию, недоверия разработчиков (вы знаете, диверсантов, которые, похоже, преследуют команды Java ...), совместимости с предыдущие JVM, время для написания правильной спецификации и т.д ..
Так что не задерживайте дыхание в ожидании этой функции ...
Но они делают это на C # !!!
Да ...
Хотя это далеко не единственное различие между двумя языками, этот не может не удивить меня.
Очевидно, что люди C # со своими "каждым примитивом являются struct
, а struct
происходит от объекта" , поняли это правильно с первой попытки.
Несмотря на все FUD против используемой определенной перегрузки операторов, следующие языки поддерживают ее: Scala , Dart , Python , F #, C # , D , Algol 68 , Smalltalk , Groovy , Perl 6, C ++, Рубин , Haskell , MATLAB , Eiffel , Lua , Clojure, Фортран 90 , Свифт , Ада , Delphi 2005 ...
Так много языков, с таким количеством разных (и иногда противоположных) философий, и все же они все согласны с этим.
Пища для размышлений ...