Есть много постов с жалобами на перегрузку операторов.
Я чувствовал, что должен прояснить концепции «перегрузки операторов», предлагая альтернативную точку зрения на эту концепцию.
Код запутывает?
Этот аргумент является ошибкой.
Обфускация возможна на всех языках ...
Код в 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 » и « объекта C ++ Matrix »:
// 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 # с их «каждым примитивом a struct
и struct
производным от Object» сделали это правильно с первой попытки.
Несмотря на все FUD против используемой определенной перегрузки операторов, ее поддерживают следующие языки: Scala , Dart , Python , F # , C # , D , Algol 68 , Smalltalk , Groovy , Perl 6 , C ++, Ruby , Haskell , MATLAB , Eiffel , Lua , Clojure , Fortran 90 , Swift , Ada , Delphi 2005 ...
Так много языков, так много разных (и иногда противоположных) философий, и все же они все согласны с этим.
Пища для размышлений ...