Дуже багато повідомлень скаржиться на перевантаження оператора.
Я відчув, що мені доведеться уточнити поняття "перевантаження оператора", пропонуючи альтернативну точку зору на цю концепцію.
Код обманюючий?
Цей аргумент є помилкою.
Обманювання можливе на всіх мовах ...
Заблудити код на 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 " та " Об'єкт матриці C ++ ":
// 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 мають (жорстко кодований) оператор перевантажений
У C реальна поведінка оператора зміниться відповідно до його операндів. Наприклад, додавання двох цілих чисел відрізняється, ніж додавання двох парних чи навіть одного цілого числа та одного подвійного. Існує навіть цілий арифметичний домен вказівника (без кастингу ви можете додати вказівник ціле число, але ви не можете додати два покажчики ...).
У 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
Порівняйте текст Гослінга вище із Stroustrup нижче:
Багато дизайнерських рішень C ++ мають коріння в моїй неприязні до того, щоб змусити людей робити речі якимось особливим чином [...] Часто мене спокушало заборонити функцію, яку мені особисто не подобалося, я утримався від цього, бо не думав, що маю право насилювати мої погляди на інших .
Bjarne Stroustrup. Джерело: Дизайн та еволюція 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 ++, Ruby , Haskell , MATLAB , Eiffel , Lua , Clojure , Fortran 90 , Swift , Ada , Delphi 2005 ...
Стільки мов з такою кількістю різних (а іноді і протилежних) філософій, і все ж вони згодні з цим.
Їжа для роздумів...