Что делает перегрузку оператора Scala «хорошей», а «плохой» в C ++?

155

Многие считают, что перегрузка операторов в C ++ является «плохой вещью» (tm), и это ошибка, которую нельзя повторять на более новых языках. Конечно, это была одна особенность, специально упущенная при разработке Java.

Теперь, когда я начал читать Scala, я обнаружил, что он имеет то, что очень похоже на перегрузку операторов (хотя технически у него нет перегрузки операторов, потому что у него нет операторов, только функции). Тем не менее, он не будет качественно отличаться от перегрузки операторов в C ++, где, насколько я помню, операторы определяются как специальные функции.

Поэтому мой вопрос заключается в том, что делает идею определения «+» в Scala лучшей идеей, чем в C ++?

skaffman
источник
27
Ни C ++, ни Scala не были определены универсальным консенсусом среди всех программистов. Я не думаю, что есть какое-то противоречие между тем, что некоторые люди жалуются на C ++, и тем, что некоторые люди не жалуются на Scala.
Стив Джессоп
16
В C ++ нет ничего плохого в перегрузке операторов.
Щенок
5
В этом нет ничего нового, но способ, которым я защищаю C ++, когда перегрузка операторов и другие «продвинутые» функции ставятся под сомнение, прост: C ++ дает нам все возможности использовать / злоупотреблять им по своему усмотрению. Мне всегда нравилось то, что мы предполагаем быть компетентными и автономными и не нуждаемся в подобных решениях, принятых нами за нас.
Эллиотт
Scala был разработан как десятилетия после C ++. Оказывается, что человек, стоящий за этим, является суперсовременным с точки зрения языков программирования. В этом нет ничего плохого, если вы будете придерживаться c ++ или Scala еще 100 лет, станет ясно, что, вероятно, оба они плохие! Быть предвзятым, по-видимому, в нашей природе, но мы можем с этим бороться, просто взглянем на историю технологий, все становится устаревшим.
Надер

Ответы:

242

C ++ наследует истинные синие операторы от C. Под этим я подразумеваю, что «+» в 6 + 4 очень особенный. Например, вы не можете получить указатель на эту + функцию.

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

Как ни крути, перегрузка операторов не так уж плоха, даже в C ++. Проблема в том, что плохие программисты злоупотребляют этим. Но, честно говоря, я придерживаюсь мнения, что устранение способности программистов злоупотреблять перегрузкой операторов не дает оснований исправлять все то, что программисты могут злоупотреблять. Настоящий ответ - наставничество. http://james-iry.blogspot.com/2009/03/operator-overloading-ad-absurdum.html

Тем не менее, существуют различия между перегрузкой операторов C ++ и гибким именованием методов Scala, которые, IMHO, делают Scala менее уязвимым и более уязвимым.

В C ++ единственный способ получить фиксированную запись - использовать операторы. В противном случае вы должны использовать object.message (аргумент) или pointer-> messsage (аргумент) или функцию (аргумент1, аргумент2). Так что если вы хотите определенный стиль DSLish для своего кода, тогда есть необходимость использовать операторы.

В Scala вы можете получить инфиксную запись с любым отправленным сообщением. «Аргумент объекта сообщения» совершенно нормально, что означает, что вам не нужно использовать несловарные символы только для того, чтобы получить инфиксную запись.

Перегрузка операторов C ++ ограничена по существу операторами C. В сочетании с ограничением, что только операторы могут использовать инфикс, который заставляет людей пытаться отобразить широкий спектр несвязанных понятий на относительно небольшое количество символов, таких как «+» и «>>»

Scala допускает огромный диапазон допустимых несловесных символов в качестве имен методов. Например, у меня есть встроенный DSL Пролог, где вы можете написать

female('jane)!         // jane is female
parent('jane,'john)!   // jane is john's parent
parent('jane, 'wendy)! // jane is wendy's parent

mother('Mother, 'Child) :- parent('Mother, 'Child) & female('Mother) //'// a mother of a child is the child's parent and is female

mother('X, 'john)?  // find john's mother
mother('jane, 'X)?  // find's all of jane's children

Символы: -,!,? И & определяются как обычные методы. В C ++ только & будет допустимым, поэтому попытка отобразить этот DSL в C ++ потребует некоторых символов, которые уже вызывают совершенно разные понятия.

Конечно, это также открывает Scala для другого вида злоупотреблений. В Scala вы можете назвать метод $! & ^%, Если хотите.

Для других языков, которые, например, Scala, являются гибкими в использовании несловесных функций и имен методов, см. Smalltalk, где, как и Scala, каждый «оператор» - это просто еще один метод, и Haskell, который позволяет программисту определять приоритет и фиксированность гибко именованных имен. функции.

Джеймс Ири
источник
В последний раз я проверял, 3. оператор + (5) работал. Я действительно удивлен, что & (3.operator +) нет.
Джошуа
Например, вы можете сделать assert (female ("jane")) в c ++. Это совсем не смущает - кивок на джеймс-ирский пост о том, что оператор + - это плохо, а программисты глупы.
pm100
1
int main() {return (3).operator+(5);}Результаты @Joshuaerror: request for member ‘operator+’ in ‘3’, which is of non-class type ‘int’
zildjohn01
Это куча высокомерного дерьма: «Перегрузка операторов не так уж плоха, даже в C ++. Проблема в том, что плохие программисты злоупотребляют этим». Если чем-то легко злоупотребить с довольно незначительными выгодами от его использования, общий результат состоит в том, что следующий парень, обслуживающий ваш код, потеряет производительность в расшифровке более странных частей вашего кода. В противном случае: очень информативный и хорошо написанный ответ.
Юкка Далбом
@JukkaDahlbom Наличие умных указателей делает выгоду большой самостоятельно. И тогда у вас есть лямбды, определяемые пользователем типы чисел, типы интервалов ...
Алексей Романов
66

Многие считают, что перегрузка операторов в C ++ - это плохо (tm)

Только по невежеству. Это абсолютно необходимо в таких языках, как C ++, и заметно, что другие языки, которые начали с «пуристического» взгляда, добавили его, как только их разработчики выяснили, насколько это необходимо.


источник
30
Я на самом деле согласен с Нилом. Перегрузка операторов необходима, если вы хотите представить переменные / константы / объекты / экземпляры как алгебраические сущности ... и чтобы люди понимали свои взаимодействия математическим образом - так должно быть, программирование работает IMHO.
Масса
16
+1, перегрузка оператора в C ++ - это хорошо. Например, это делает векторную математику намного чище. Как и во многих функциях C ++, вы должны осторожно использовать возможности.
Джон Смит
7
@ Kristo Потому что C ++ использует значения, которые должны быть назначены и скопированы. Необходимо иметь контроль над этим, поэтому вы должны иметь возможность указать оператор присваивания для данного типа, как минимум.
7
@Kristo: потому что одно из намерений C ++ - позволить пользовательским типам делать все, что делают встроенные типы (хотя они обрабатываются по-разному в некоторых контекстах, таких как неявные преобразования). Если вы хотите реализовать 27-битное целое число, тогда вы можете, и использовать его будет так же, как использовать int. Без перегрузки операторов было бы невозможно использовать UDT с тем же синтаксисом, что и для встроенных типов, и, следовательно, результирующий язык не будет "похож на C ++" в этом смысле.
Стив Джессоп
8
«этот путь - безумие» - еще хуже, что этот путь лежит как std :: vector <bool>!
Стив Джессоп
42

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

Преимущества и недостатки перегрузки операторов в Scala такие же, как и в C ++ - вы можете написать более естественный код, если вы правильно используете перегрузку операторов, - и более загадочный, запутанный код, если вы этого не сделаете.

К сведению: операторы не определены как специальные функции в C ++, они ведут себя так же, как и любая другая функция - хотя есть некоторые различия в поиске имен, должны ли они быть функциями-членами, и тот факт, что их можно вызывать двумя способами: 1 ) синтаксис оператора и 2) синтаксис идентификатора оператора-функции.

Фейсал Вали
источник
«На самом деле не нужно перегружать операторы в языке, так как в любом случае их можно смоделировать с помощью более подробных вызовов функций». При такой логике даже не нужны операторы . Почему бы просто не использовать add(2, multiply(5, 3))?
Джо З.
Это скорее случай совпадения с обычными обозначениями. Рассмотрим математиков и физиков, они могут понять и использовать библиотеку C ++, которая гораздо легче обеспечивает перегрузки операторов. Они скорее сконцентрируются на уравнении, чем на языке программирования.
Фил Райт
19

Эта статья - « Позитивное наследие C ++ и Java » - напрямую отвечает на ваш вопрос.

«C ++ имеет как выделение стека, так и выделение кучи, и вы должны перегружать свои операторы, чтобы обрабатывать все ситуации и не вызывать утечек памяти. Действительно, сложно. Однако в Java есть единый механизм выделения памяти и сборщик мусора, что делает перегрузку операторов тривиальной». ..

Java по ошибке (по мнению автора) пропустила перегрузку операторов, потому что это было сложно в C ++, но забыла почему (или не поняла, что это не относится к Java).

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

jmanning2k
источник
14
Экель - единственный источник, который я когда-либо видел за идею, что перегрузка операторов была исключена из Java из-за сложностей в C ++, и он не говорит, каков его источник. Я бы обесценил это. Все остальные источники, о которых я говорил, были исключены из-за возможного злоупотребления. См. Gotw.ca/publications/c_family_interview.htm и newt.com/wohler/articles/james-gosling-ramblings-1.html . Просто найдите их на странице "оператор перегрузки".
Джеймс Айри
9

В перегрузке операторов нет ничего плохого. На самом деле, что-то не так с отсутствием перегрузки операторов для числовых типов. (Взгляните на некоторый код Java, который использует BigInteger и BigDecimal.)

C ++ имеет традицию злоупотреблять этой функцией. Часто цитируемый пример - операторы сдвига битов перегружены для выполнения операций ввода-вывода.

dan04
источник
Операторы << и >> визуально указывают способ передачи, они предназначены для ввода / вывода, это не злоупотребление, это стандартная библиотека и практическая вещь. Просто посмотрите на "cin >> что-то", что идет куда? Очевидно, от чина до чего-то.
peenut
7
@peenut: Но их первоначальное использование было сдвигом. «Стандартная библиотека» использует оператор таким образом, что полностью смешивается с исходным определением.
Джо З.
1
Я уверен, что где-то читал, что Бьярн Страуструп (создатель C ++) экспериментировал с использованием =вместо <<и >>в первые дни C ++, но столкнулся с проблемами, поскольку у него не было правильного приоритета оператора (т.е. он ищет аргументы слева или справа вначале). Так что его руки были немного связаны с тем, что он мог использовать.
Фил Райт
8

В целом это не плохо.
Новые языки, такие как C #, также имеют перегрузку операторов.

Злоупотребление перегрузкой операторов - это плохо.

Но есть также проблемы с перегрузкой операторов, как это определено в C ++. Поскольку перегруженные операторы являются просто синтаксическим сахаром для вызовов методов, они ведут себя так же, как методы. С другой стороны, обычные встроенные операторы не ведут себя как методы. Эти несоответствия могут быть причиной проблем.

С верхней части моей головы операторов ||и &&.
Встроенные версии этих операторов являются сокращенными. Это не относится к перегруженным версиям и вызвало некоторые проблемы.

Тот факт, что + - * / все возвращают тот же тип, с которым они работают (после повышения
статуса оператора) . Перегруженные версии могут возвращать что угодно (Вот где начинается злоупотребление, если ваши операторы начинают возвращать некоторый тип арбитра, которого пользователь не ожидал дела идут вниз по склону).

Мартин Йорк
источник
8

Перегрузка операторов - это не то, что вам действительно «нужно» очень часто, но при использовании Java, если вы попадаете в точку, в которой вы действительно нуждаетесь, вы захотите вырвать ногти, чтобы у вас был повод прекратить печатать ,

Тот код, который вы только что нашли, давно переполнен? Да, вам придется перепечатать всю партию, чтобы она работала с BigInteger. Нет ничего более разочаровывающего, чем необходимость изобретать велосипед просто для изменения типа переменной.


источник
6

Гай Стил утверждал, что перегрузка операторов должна быть и в Java, в своей основной речи «Развитие языка» - есть видео и его транскрипция, и это действительно потрясающая речь. Вам будет интересно, о чем он говорит на первых двух страницах, но если вы продолжите читать, вы увидите смысл и достигнете просветления. И тот факт, что он вообще мог выступить с такой речью, тоже удивителен.

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

Возвращаясь к делу, его примеры в основном касаются числовых классов (например, BigInteger и некоторых странных вещей), но это не обязательно.

Правда, злоупотребление перегрузкой операторов может привести к ужасным результатам, и даже правильное использование может усложнить ситуацию, если вы попытаетесь прочитать код, не изучая немного библиотек, которые он использует. Но разве это хорошая идея? OTOH, не должны ли такие библиотеки попытаться включить шпаргалку для своих операторов?

Blaisorblade
источник
4

Я верю, что КАЖДЫЙ ответ пропустил это. В C ++ вы можете перегружать операторы сколько хотите, но вы не можете влиять на приоритет, с которым они оцениваются. У Scala нет этой проблемы, IIRC.

Что касается того, что это плохая идея, помимо проблем с приоритетами, люди придумывают действительно глупые значения для операторов, и это редко способствует удобочитаемости. Библиотеки Scala особенно плохи для этого, глупые символы, которые вы должны запоминать каждый раз, когда администраторы библиотек торчат головой в песке, говоря: «Вам нужно выучить это только один раз». Отлично, теперь мне нужно выучить загадочный синтаксис "умного" автора * количество библиотек, которые я хочу использовать. Это не было бы так плохо, если бы существовало соглашение ВСЕГДА, предоставляющее грамотную версию операторов.

Saem
источник
1
В Scala также зафиксирован приоритет операторов, не так ли?
Скаффман
Я верю, что есть, но это намного лестнее. Более того, у Scala меньше операторов. +, -, * - это методы, а не операторы, IIRC. Вот почему 2 + 3 * 2, не 8, а 10.
Сем
7
Scala имеет систему приоритетов, основанную на первом символе символа. scala> 2 + 3 * 2 res0: Int = 8
Джеймс Ири
3

Перегрузка операторов не была изобретением C ++ - она ​​пришла от Algol IIRC, и даже Гослинг не утверждает, что это плохая идея в целом.

Неманья Трифунович
источник
Конечно, но в своем воплощении C ++ он приобрел общий вид неуверенности.
Скаффман
5
Что вы имеете в виду под "общим видом неуверенности"? Большинство моих знакомых используют языки, поддерживающие перегрузку операторов (C ++, C #), и я никогда не слышал никаких жалоб.
Неманя Трифунович
Я говорю из своего давнего опыта работы с pre-ANSI C ++ и, конечно же, вспоминаю их общую неприязнь. Возможно, ситуация улучшилась с ANSI C ++, или люди просто научились не злоупотреблять этим.
Скаффман
1
Говоря как кто-то, кто использует C ++ со времен cfront (середина 80-х), я могу заверить вас, что введение стандарта ISO не повлияло на предрассудки людей относительно перегрузки операторов.
3

Единственное, что неправильно известно в C ++ - это отсутствие возможности перегрузить [] = как отдельный оператор. Это может быть трудно реализовать в компиляторе C ++, что, вероятно, не является очевидной причиной, но многое стоит того.

Джошуа
источник
2

Как указали другие ответы; перегрузка оператора сама по себе не обязательно плохая. Что плохо, когда он используется таким образом, что результирующий код неочевиден. Как правило, при их использовании вы должны заставить их делать наименьшую удивительную вещь (разделение оператор + делание может создать проблемы для рационального использования класса) или, как говорит Скотт Мейерс:

Клиенты уже знают, как ведут себя типы, такие как int, поэтому вы должны стремиться к тому, чтобы ваши типы вели себя одинаково, когда это разумно ... Если есть сомнения, поступайте так же, как и int . (Из Эффективного C ++, 3-е издание, пункт 18)

Теперь некоторые люди довели перегрузку операторов до крайности такими вещами, как boost :: spirit . На этом уровне вы понятия не имеете, как это реализовано, но он делает интересный синтаксис, чтобы получить то, что вы хотите сделать. Я не уверен, хорошо это или плохо. Вроде неплохо, но я этим не пользовался.

Мэтт Прайс
источник
Я не спорю за или против перегрузки операторов здесь, я не ищу людей, чтобы оправдать их.
Скаффман
Sprint не подходит ни к какому худшему примеру, с которым мне приходилось сталкиваться - вы должны увидеть, что делает библиотека баз данных RogueWave!
Я согласен, что Spirit злоупотребляет операторами, но я не могу придумать лучшего способа сделать это.
Зифре
1
Я не думаю, что дух сильно оскорбляет операторов, но подталкивает его. Я согласен, что действительно нет другого способа сделать это. Это в основном создает DSL в синтаксисе C ++. Очень далеко от того, для чего был разработан C ++. Да, есть гораздо худшие примеры :) В общем, я использую их там, где это уместно. В основном только потоковые операторы для облегчения отладки \ регистрации. И даже там это просто сахар, который направляет метод, реализованный в классе.
Мэтт Прайс
1
Это вопрос вкуса; но библиотеки комбинаторов синтаксического анализатора на функциональных языках перегружают операторы способом, очень похожим на Spirit, и никто не спорит с этим. Есть много технических причин, по которым они лучше: Google для «встроенных специфичных для домена языков» нашел множество статей, объясняющих это с общей точки зрения, и Google для «scala parser combinator» для практических примеров в этом случае. Это правда, что в функциональных языках результирующий синтаксис часто лучше, например, вам не нужно менять значение >> для объединения синтаксических анализаторов.
Blaisorblade
2

Я никогда не видел статьи, утверждающей, что перегрузка операторов C ++ - это плохо.

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

Пол Натан
источник
1

Тем не менее, он не будет качественно отличаться от перегрузки операторов в C ++, где, насколько я помню, операторы определяются как специальные функции.

AFAIK, нет ничего особенного в операторских функциях по сравнению с "нормальными" функциями-членами. Конечно, у вас есть только определенный набор операторов, которые вы можете перегружать, но это не делает их особенными.

Джон Смит
источник