Что такое перегрузка операторов

В ста­тье про С++ мы упо­ми­на­ли пере­груз­ку опе­ра­то­ров. Это мощ­ный и гиб­кий инстру­мент, кото­рый может ока­зать­ся опас­ным и непред­ска­зу­е­мым в неуме­лых руках. Наста­ло вре­мя разобраться.

👉 Опыт­ным про­грам­ми­стам: мы наме­рен­но упро­стим дета­ли для пони­ма­ния сути. Ну сорян.

На примере сложения

Во всех язы­ках есть опе­ра­тор «плюс» — обыч­но он уме­ет скла­ды­вать чис­ла и соеди­нять строки:

2 + 2 = 4
2.2 + 1.8 = 4.0
−2 + 2 = 0
−2 + 0 = –2
−2 + −2 = −4
‘2’ + ‘2’ = ‘22’
‘два’ + ‘два’ = ‘два­д­ва’
‘четы’ + ‘ре’ = ‘четы­ре’

Допу­стим, мы пишем софт для интернет-магазина, и у нас есть там класс объ­ек­тов «заказ». Напом­ним, что класс — это как бы чер­тёж, по кото­ро­му созда­ют­ся объ­ек­ты. А объ­ект — это такая короб­ка с дан­ны­ми и функ­ци­я­ми, кото­ры­ми мы можем управ­лять как еди­ным целым. Подроб­нее об этом — в ста­тьях про объ­ек­ты и клас­сы.

В объ­ек­те типа «заказ» лежит куча всего:

  • мас­сив с содер­жи­мым корзины,
  • дата и вре­мя, когда сфор­ми­ро­ван заказ,
  • метод «очи­стить корзину»,
  • место для промокода,
  • метод «при­ме­нить промокод»,
  • метод «про­ве­рить нали­чие това­ров по складу»
  • иден­ти­фи­ка­тор пользователя,
  • что-нибудь ещё интересное.

Допу­стим, наша систе­ма устро­е­на так, что у любо­го зака­за может быть два иден­ти­фи­ка­то­ра поль­зо­ва­те­ля: посто­ян­ный или временный.

  • Если иден­ти­фи­ка­тор посто­ян­ный, зна­чит, мы одно­знач­но поль­зо­ва­те­ля узна­ли (его адрес, номер кре­дит­ки и т. д.).
  • Если иден­ти­фи­ка­тор вре­мен­ный, зна­чит, мы не зна­ем, что за поль­зо­ва­тель — про­сто хра­ним его кор­зи­ну, пока он не офор­мит заказ. Это может быть новый чело­век или ста­рый, но ещё не зало­ги­нив­ший­ся. В любом слу­чае мы долж­ны хра­нить его данные.

В какой-то момент поль­зо­ва­тель с вре­мен­ным иден­ти­фи­ка­то­ром логи­нит­ся в систе­му, и нам хочет­ся сде­лать сле­ду­ю­щую операцию:

zakaz_5058303 + zakaz_user121239

Обе части выра­же­ния — это объ­ек­ты клас­са «Заказ». А наш язык про­грам­ми­ро­ва­ния не зна­ет, что зна­чит «сло­жить два объ­ек­та клас­са „Заказ“». Он не знает:

  • Что с чем скла­ды­вать? Чис­ло това­ров? Сум­мы? Номе­ра това­ров? Номе­ра теле­фо­нов? Ведь язык не пони­ма­ет, что за объ­ект перед ним. Для него это про­сто короб­ка с дан­ны­ми, он может с ними делать что хочешь.
  • Что воз­вра­щать? Чис­ло? Стро­ку? Объ­ект? Спи­сок заказов?
  • Может быть, нуж­но срав­нить два зака­за и по каким-то кри­те­ри­ям опре­де­лить самый актуальный?
  • Или нуж­но объ­еди­нить две кор­зи­ны в одну?
  • А что тогда делать с повто­ря­ю­щи­ми­ся това­ра­ми? Заме­нить? Доба­вить в коли­че­ство? Проигнорировать?

Вро­де бы про­стая опе­ра­ция — а столь­ко вопро­сов. Вот это­му все­му мы можем обу­чить опе­ра­тор «+», и это будет пере­груз­ка оператора.

👉 Короче

Пере­груз­ка опе­ра­то­ра — это когда мы обу­ча­ем язык про­грам­ми­ро­ва­ния, как опе­ра­то­ру типа плюс, минус, умно­жить и т. д. вести себя с опре­де­лён­ным типом ввод­ных — напри­мер, с объ­ек­та­ми, мат­ри­ца­ми или картинками.

В слу­чае с нашим при­ме­ром мы можем ска­зать, что если скла­ды­ва­ют­ся два зака­за, делай следующее:

  1. Най­ди, какой из этих зака­зов постоянный.
  2. Пере­ло­жи из вре­мен­но­го в посто­ян­ный все уни­каль­ные товары.
  3. Если есть неуни­каль­ные това­ры (напри­мер, и в том, и в дру­гом зака­зе была одна и та же пози­ция), склей их и поставь мак­си­маль­ное коли­че­ство. Напри­мер, если в посто­ян­ном зака­зе сто­я­ло 3 шту­ки одно­го арти­ку­ла, а во вре­мен­ном это­го же арти­ку­ла 9 штук, то поставь в посто­ян­ный 9 штук.
  4. Вре­мен­но­му зака­зу поставь ста­тус «Скле­е­но».
  5. Зало­ги­руй вре­мя склей­ки заказов.
  6. Вер­ни посто­ян­ный заказ с обнов­лён­ны­ми данными.

Доволь­но мно­го дей­ствий для одно­го плю­са, не находите?

Что хорошего в перегрузке

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

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

Чем опасна перегрузка операторов

Когда вы исполь­зу­е­те в коде пере­гру­жен­ный опе­ра­тор, он выгля­дит как самый обыч­ный опе­ра­тор. Ну скла­ды­ва­ет и скла­ды­ва­ет. Ну умно­жа­ет и умно­жа­ет, чего такого-то?

Это, с одной сто­ро­ны, эле­гант­но. А с дру­гой, созда­ёт про­бле­мы в отладке.

  1. Пред­ставь­те, что после вас какой-то про­грам­мист пере­де­лал струк­ту­ру клас­са «Заказ», и теперь там по-другому рабо­та­ет мас­сив с товар­ны­ми пози­ци­я­ми. Рань­ше у това­ров были чис­ло­вые иден­ти­фи­ка­то­ры типа integer (целые чис­ла), а новый про­грам­мист пере­де­лал их на строки.
  2. Ваш язык в неяв­ном виде под­дер­жи­ва­ет срав­не­ние чисел со стро­ка­ми и наобо­рот. Он про­из­во­дит какие-то свои пре­об­ра­зо­ва­ния и поз­во­ля­ет срав­нить чис­ло со стро­кой. В 99,9% слу­ча­ев это не сло­ма­ет вашу про­грам­му, и даже пере­гру­жен­ный опе­ра­тор будет работать.
  3. Но в 0,1% слу­ча­ев срав­не­ние слу­чит­ся некор­рект­но, и никто не будет пони­мать, в чём дело. Где-то под капо­том пере­гру­жен­ный опе­ра­тор некор­рект­но скле­и­ва­ет спис­ки поку­пок, у поль­зо­ва­те­ля выва­ли­ва­ют­ся какие-то «левые» това­ры, кото­рых он не зака­зы­вал. Он не гля­дя их опла­чи­ва­ет и потом ката­ет жало­бу на ваш магазин.

А ещё, в осо­бо экзо­ти­че­ских слу­ча­ях и боль­ших про­ек­тах, про­грам­мист шут­ки ради может пере­гру­зить опе­ра­тор сло­же­ния так, что он будет не скла­ды­вать, а вычи­тать. И заме­тить, в чём тут ошиб­ка, в таких слу­ча­ях быва­ет очень сложно.

И что?

Пере­груз­ка опе­ра­то­ров — это полез­но, но сложно.

Если про­грам­мист не пони­ма­ет пол­но­стью меха­низ­ма рабо­ты пере­гру­зок, луч­ше не перегружать.

Если пони­ма­ет — он моло­дец и может учить стан­дарт­ные инстру­мен­ты нестан­дарт­но­му поведению.

Не пере­гру­жай­тесь, бере­ги­те себя.