Более фундаментальная причина, по которой Java не включает перегрузку операторов (по крайней мере, для присваивания)? - PullRequest
3 голосов
/ 16 мая 2011

Ссылаясь на двухлетнее обсуждение того факта, что в Java нет перегрузки операторов ( Почему Java не предлагает перегрузку операторов? ), и я сам пришел из многих интенсивных лет C ++ к Java, мне интересно, есть ли более фундаментальная причина, по которой перегрузка операторов не является частью языка Java, по крайней мере, в случае присваивания, чем ответ с наивысшим рейтингом в этих состояниях ссылок в нижней части ответа (а именно, что это был личный выбор Джеймса Гослинга).

В частности, рассмотрите назначение.

// C++
#include <iostream>

class MyClass
{
public:
    int x;
    MyClass(const int _x) : x(_x) {}
    MyClass & operator=(const MyClass & rhs) {x=rhs.x; return *this;}
};

int main()
{
    MyClass myObj1(1), myObj2(2);
    MyClass & myRef = myObj1;
    myRef = myObj2;

    std::cout << "myObj1.x = " << myObj1.x << std::endl;
    std::cout << "myObj2.x = " << myObj2.x << std::endl;

    return 0;
}

Вывод:

myObj1.x = 2
myObj2.x = 2

Однако в Java строка myRef = myObj2 (при условии, что объявление myRef в предыдущей строке было myClass myRef = myObj1, как того требует Java, поскольку все такие переменные автоматически являются «ссылками» в стиле Java), ведет себя очень по-разному - это не приведет к изменению myObj1.x, и результат будет

myObj1.x = 1
myObj2.x = 2

Это различие между C ++ и Java заставляет меня думать, что отсутствие перегрузки операторов в Java, по крайней мере, в случае присваивания, является не «вопросом личного выбора» со стороны Джеймса Гослинга, а скорее фундаментальным необходимость, учитывая синтаксис Java, который обрабатывает все переменные объекта как ссылки (то есть MyClass myRef = myObj1 определяет myRef как ссылку в стиле Java). Я говорю это потому, что если при присваивании в Java левая ссылка ссылается на другой объект, а не допускает возможность того, что сам объект изменит свое значение, то может показаться, что нет возможности предоставить перегруженный оператор присваивания .

Другими словами - это не просто «выбор», и даже нет возможности «задержать дыхание» с надеждой на то, что он когда-либо будет представлен, как сказано в вышеупомянутом высококлассном ответе (ближе к концу ). Цитата: " Причинами, по которым их нельзя добавлять сейчас, может быть сочетание внутренней политики, аллергии на эту функцию, недоверия разработчиков (вы знаете, диверсантов), совместимости с предыдущими JVM, времени для написания правильной спецификации. и т. д. Так что не ждите этой функции.". <- Так что это не правильно, по крайней мере, для оператора присваивания: причина, по которой нет перегрузки операторов (по крайней мере, для присваивания), является фундаментальной для природы Java. </p>

Это правильная оценка с моей стороны?

ДОПОЛНЕНИЕ

Если предположить, что оператор присваивания является особым случаем, тогда мой следующий вопрос: есть ли какие-либо другие операторы или, в более общем смысле, какие-либо другие языковые функции, которые по необходимости будут затронуты аналогично оператору присваивания? Я хотел бы знать, насколько глубока разница между Java и C ++ в отношении переменных-как-значений / ссылок. т. е. в C ++ переменные токены представляют значения (и обратите внимание, что даже если переменный токен был первоначально объявлен как ссылка, он по-прежнему рассматривается как значение, по существу, везде, где он используется), тогда как в Java переменные токены представляют честные ссылки на добродетели везде, где токен используется позже.

Ответы [ 4 ]

5 голосов
/ 16 мая 2011

При обсуждении сходства и различий между Java и C ++ возникает большое заблуждение, которое возникает в вашем вопросе.Ссылки на C ++ и ссылки на Java не совпадают.В Java ссылка - это сбрасываемый прокси для реального объекта, а в C ++ ссылка - это псевдоним для объекта.Чтобы выразить это в терминах C ++, ссылки на Java - это указатель для сборки мусора, а не ссылка.Теперь, возвращаясь к вашему примеру, чтобы написать эквивалентный код на C ++ и Java, вы должны будете использовать указатели:

int main() {
   type a(1), b(2);
   type *pa = &a, *pb = &b;
   pa = pb;
   // a is still 1, b is still 2, pa == pb == &b
}

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

Если бы вам было позволено перегрузить operator= для определенного объекта в Java, вы бы не сталиможет иметь несколько ссылок на один и тот же объект, и язык будет поврежден:

Type a = new Type(1);
Type b = new Type(2);
a = b;                 // dispatched to Type.operator=( Type )??
a.foo();
a = new Type(3);       // do you want to copy Type(3) into a, or work with a new object?

Это, в свою очередь, сделает тип непригодным для использования в языке: контейнеры хранят ссылки, и они переназначают их (даже первыевремя, когда объект создается), функции на самом деле не используют семантику передача по ссылке , а скорее передачу по значению ссылки (что является совершенно другой проблемой)опять же, разница составляет void foo( type* ) против void foo( type& ): прокси-объект копируется, вы не можете изменить ссылку , переданную вызывающей стороной.

Проблема в том, что языкочень стараюсь скрыть тот факт, что a и объект, на который a ссылается , не одно и то же (то же самое происходит в C #), и что яn очередь означает, что вы не можете явно указать, что к ссылке / референту должна применяться одна операция, которая разрешается языком.Результатом этого проекта является то, что любая операция, которая может быть применена к ссылкам, никогда не может быть применена к самим объектам.

Что касается остальных операторов, решение, скорее всего, произвольно Поскольку язык скрывает разницу между ссылками и объектами, его можно было бы спроектировать так, чтобы компилятор преобразовал a+b в type* operator+( type*, type* ).Поскольку вы не можете использовать арифметику, проблем не будет, так как компилятор распознает, что a+b - это операция, которая должна применяться к объектам (это не имеет смысла со ссылками).Но тогда может показаться немного неловким, что вы можете перегрузить +, но вы не можете перегрузить =, ==, != ...

Это путь, по которому C # пошел, гденазначение не может быть перегружено для ссылочных типов.Интересно, что в C # есть типы значений, и набор операторов, которые могут быть перегружены для ссылочных типов и типов значений, различается.Не кодируя C # в больших проектах, я не могу точно сказать, является ли этот потенциальный источник путаницы таковым или люди просто привыкли к нему (но если вы будете искать SO, вы обнаружите, что несколько человек спрашивают , почему X не можетбыть перегруженным в C # для типов ссылок , где X является одной из операций, которые могут быть применены к самой ссылке.

3 голосов
/ 16 мая 2011

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

Есть некоторые вещи, от которых я себя чувствую, например, перегрузка операторов. Я исключил перегрузку операторов как личный выбор, потому что видел, как слишком много людей злоупотребляют этим в C ++. В последние пять-шесть лет я проводил много времени, опрашивая людей на предмет перегрузки операторов, и это действительно увлекательно, потому что вы разбили сообщество на три части: вероятно, от 20 до 30 процентов населения считают перегрузку операторов как порождение дьявола; кто-то сделал что-то с перегрузкой операторов, что просто пометило их галочкой, потому что они использовали как + для вставки списка, и это делает жизнь по-настоящему запутанной. Большая часть этой проблемы проистекает из того факта, что есть только приблизительно полдюжины операторов, которые вы можете ощутимо перегрузить, и все же есть тысячи или миллионы операторов, которые люди хотели бы определить - так что вы должны выбирать, и часто выбор конфликт с вашим чувством интуиции. Тогда есть сообщество приблизительно 10 процентов, которые фактически использовали перегрузку операторов соответственно, и кто действительно заботится об этом, и для кого это действительно действительно важно; это почти исключительно люди, которые выполняют числовую работу, где нотация очень важна для обращения к интуиции людей, потому что они приходят к ней с интуицией о том, что означает +, и способностью сказать «a + b», где a и b комплексные числа или матрицы или что-то действительно имеет смысл. Вы становитесь немного шаткими, когда сталкиваетесь с такими вещами, как умножение, потому что на самом деле есть несколько видов операторов умножения - есть векторное произведение и точечное произведение, которые принципиально отличаются друг от друга. И все же есть только один оператор, так что вы делаете? И нет оператора для квадратного корня. Эти два лагеря являются полюсами, а затем в середине 60 с лишним процентов есть этот месиво, которого в любом случае не волнует. Лагерь людей, которые думают, что перегрузка операторов - плохая идея, была просто из моей неофициальной статистической выборки, значительно больше и, конечно, более громкая, чем числовые ребята. Итак, учитывая то, как сегодня идут дела, когда сообщество голосует за некоторые функции в языке - это не просто небольшой комитет по стандартам, а действительно крупномасштабный - было бы довольно сложно получить перегрузку операторов в. И все же это оставляет это единственное сообщество довольно важных людей вроде как полностью закрытыми. Это вкус трагедии проблемы общего достояния.

ОБНОВЛЕНИЕ: Re: ваше приложение, другие операторы присваивания +=, -= и т. Д. Также будут затронуты. Вы также не можете написать swap функцию, такую ​​как void swap(int *a, int *b);. и другие вещи.

1 голос
/ 16 мая 2011

Это правильная оценка с моей стороны?

Отсутствие оператора в целом - это «личный выбор». C #, очень похожий язык, допускает перегрузку операторов. Но вы все равно не можете перегрузить присвоение . Что бы это вообще делало на языке ссылочной семантики?

Есть ли другие операторы или больше? вообще любые другие языковые особенности, что по необходимости будет затронуто в аналогично назначению оператор? Я хотел бы знать, как «глубокая» разница идет между Java и C ++ относительно переменные-а-значение / ссылка.

Наиболее очевидным является копирование. В языке ссылочной семантики clone() не так часто встречается и не требуется вообще для неизменяемых типов, таких как String. Но в C ++, где семантика назначений по умолчанию основана на копировании, конструкторы копирования очень распространены. И автоматически генерируется, если вы не определите один.

Более тонкое отличие состоит в том, что для языка ссылочной семантики гораздо сложнее поддерживать RAII , чем для языка семантики значений, поскольку время жизни объекта сложнее отследить. У Раймонда Чена есть хорошее объяснение.

0 голосов
/ 16 мая 2011

Причина, по которой перегрузка операторов используется в языке C ++, заключается в том, что это слишком сложная функция.Вот некоторые аспекты, которые делают его сложным:

  1. выражения представляют собой дерево
  2. перегрузка операторов - это интерфейс / документация для этих выражений
  3. интерфейсы в основном невидимыв C ++
  4. свободные функции / статические функции / дружественные функции - большой беспорядок в C ++
  5. прототипы функций уже сложная особенность
  6. выбор синтаксиса для перегрузки операторов меньше чемидеально
  7. нет другого сопоставимого API в языке c ++
  8. пользовательские типы / имена функций обрабатываются иначе, чем встроенные имена типов / функций в прототипах функций
  9. itиспользует расширенную математику, например, оператор << (ostream &, ostream & (* fptr) (ostream &)); </li>
  10. , даже в простейших примерах используется полиморфизм
  11. Это единственная функция C ++, котораяимеет двумерный массив
  12. this-указатель невидим, и важен ли выбор для ваших операторов функций-членов или вне классапрограммисты

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

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

class A { friend void f(); }; class B { friend void f(); }
void f() { /* use both A and B members inside this function */ }

Со статическими функциями вы можете сделать это:

class A { static void f(); }; void f() { /* use only class A here */ }

И со свободными функциями:

class A { }; void f() { /* you have no special access to any classes */ }

Обновление № 2: № 10, пример IДумал, что выглядит так в stdlib:

  ostream &operator<<(ostream &o, std::string s) { ... } // inside stdlib
  int main() { std::cout << "Hello World" << std::endl; }

Теперь полиморфизм в этом примере происходит потому, что вы можете выбирать между std :: cout и std :: ofstream и std :: stringstream.Это возможно, поскольку параметр operator << first принимает ссылку на ostream.В этом примере это нормальный полиморфизм времени выполнения. </p>

Обновление № 3: Все еще про прототипы.Реальное взаимодействие между перегрузкой операторов и прототипами происходит потому, что перегруженные операторы становятся частью интерфейса класса.Это подводит нас к 2d-массиву, потому что внутри компилятора интерфейс класса представляет собой 2d-структуру данных, в которой содержатся довольно сложные данные, включая логические значения, типы, имена функций.Правило № 4 необходимо для того, чтобы вы могли выбирать, когда ваши операторы находятся внутри этой 2d структуры данных и когда они находятся за ее пределами.Правило № 8 относится к логическим значениям, хранящимся в двумерной структуре данных.Правило № 7 заключается в том, что интерфейс класса используется для представления элементов дерева выражений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...