Якія асноўныя правілы і ідыёмы для перагрузкі аператара?

Заўвага. Адказы былі зададзены ў вызначаным парадку, але паколькі многія карыстальнікі сартуюць адказы ў адпаведнасці з галасамі, а не час, які яны далі, тут індэкс адказаў у тым парадку, у якой яны маюць найбольшы сэнс:

<суб> (Заўвага. Гэта азначае, што вы ўваходзіце ў Часта задаюць пытанні аб перапаўненні стэка З ++ . Калі вы хочаце крытыкаваць ідэю прадастаўлення FAQ ў гэтай форме, тады публікацыя на мета, якая пачала ўсё гэта , была б месцам для гэтага. адказы на гэтае пытанне адсочваюцца ў чаце на З ++ , дзе ідэя FAQ пачыналася ў першую чаргу, таму ваш адказ, хутчэй за ўсё, будзе прачытаны тымі, хто прыдумаў гэтую ідэю.)

1936
12 дек. зададзены sbi 12 снеж. 2010-12-12 15:44 '10 у 15:44 2010-12-12 15:44
@ 7 адказаў

Агульныя аператары для перагрузкі

Большая частка працы аператараў перагрузкі - код кацельні. Гэта нядзіўна, паколькі аператары з'яўляюцца проста сінтаксічных цукрам, іх фактычная праца можа быць выканана (і часта накіроўваецца) на простыя функцыі. Але важна, каб вы атрымалі код кацельні. Калі вы пацярпіце няўдачу, альбо код вашага аператара не будзе кампілявацца, альбо код карыстальніка не будзе кампілявацца, альбо код карыстальніка будзе выглядаць на здзіўленне.

аператар прысвойвання

Там шмат што можна сказаць аб прызначэнні. Тым не менш, большасць з іх ужо было сказана ў знакамітым "Копі-і-Swap FAQ" , таму я прапушчу большую частку гэтага тут, толькі пералічваючы ідэальны аператар прысвойвання для спасылка:

 X X::operator=(X rhs) { swap(rhs); return *this; } 

Аператары Bitshift (выкарыстоўваюцца для Stream I / O)

Аператары біт-зруху << і >> , хоць яны ўсё яшчэ выкарыстоўваюцца ў апаратным інтэрфейсе для функцый біт-маніпуляцыі, якія яны ўспадкуюць ад C, сталі больш распаўсюджанымі як перагружаныя аператары ўводу і вываду патокаў у большасці прыкладанняў. Для перагрузкі інструкцый як аператараў біт-маніпуляцыі см. Раздзел ніжэй пра двайковых арыфметычных аперацыях. Для рэалізацыі ўласнага карыстацкага фармату і логікі разбору, калі ваш аб'ект выкарыстоўваецца з iostreams, працягвайце.

Аператары патоку, сярод найбольш часта перазагружаецца аператараў, з'яўляюцца двайковымі инфиксными аператарамі, для якіх сінтаксіс не паказвае абмежаванняў на тое, ці павінны яны быць членамі або нечленами. Паколькі яны змяняюць свой левы аргумент (яны змяняюць стан патокаў), яны павінны, у адпаведнасці з эмпірычнымі правіламі, быць рэалізаваны як члены іх тыпу левых аперанд. Аднак іх левыя аперанды з'яўляюцца плынямі з стандартнай бібліятэкі, і хоць большасць аператараў вываду патоку і ўводу, вызначаных стандартнай бібліятэкай, сапраўды вызначаюцца як члены класаў патокаў, калі вы рэалізуеце аперацыі вываду і ўводу для сваіх ўласных тыпаў, вы не можа змяняць стандартныя тыпы патокаў бібліятэк . Вось чаму вам неабходна рэалізаваць гэтыя аператары для вашых уласных тыпаў як функцыі, якія не з'яўляюцца членамі. Кананічныя формы гэтых двух:

 std::ostream operator<<(std::ostream os, const T obj) { // write obj to stream return os; } std::istream operator>>(std::istream is, T obj) { // read obj from stream if(  ) is.setstate(std::ios::failbit); return is; } 

Пры рэалізацыі operator>> ўручную налада стану патокаў неабходна толькі тады, калі само чытанне выканана, але вынік не адпавядае чаканаму.

Аператар выкліку функцыі

Аператар выкліку функцыі, які выкарыстоўваецца для стварэння функцыянальных аб'ектаў, таксама вядомы як функторы, павінен быць вызначаны як функцыя member, таму ён заўсёды мае няяўнай аргумент this функцый-членаў. Акрамя гэтага, ён можа быць перагружаны, каб прымаць любую колькасць дадатковых аргументаў, уключаючы нуль.

Вось прыклад сінтаксісу:

 class foo { public: // Overloaded call operator int operator()(const std::string y) { // ... } }; 

выкарыстанне:

 foo f; int a = f("hello"); 

У стандартнай бібліятэцы З ++ аб'екты аб'ектаў заўсёды капіююцца. Таму вашыя ўласныя аб'екты функцый павінны быць таннымі для капіявання. Калі аб'екту функцыі абсалютна неабходна выкарыстоўваць дадзеныя, якія дорага капіраваць, лепш захоўваць гэтыя дадзеныя ў іншым месцы і спасылацца на аб'ект функцыі.

аператары параўнання

Аператары параўнання бінарных инфикс павінны, у адпаведнасці з правіламі вялікага пальца, быць рэалізаваны як функцыі, якія не з'яўляюцца членамі 1. Ўнітарнае прэфіксны адмаўленне ! павінна (у адпаведнасці з тымі ж правіламі) быць рэалізавана як функцыя-член. (Але, як правіла, не рэкамендуецца перагружаць яго.)

Стандартныя алгарытмы бібліятэк (напрыклад, std::sort() ) і тыпы (напрыклад, std::map ) заўсёды будуць чакаць толькі operator< . Тым не менш, карыстальнікі вашага тыпу чакаюць, што усе астатнія аператары таксама будуць прысутнічаць, таму, калі вы вызначаеце operator< , абавязкова вынікайце трэцяга асноўнаму правілу перагрузкі аператара, а таксама вызначыце ўсе астатнія лагічныя аператары параўнання. Кананічны спосаб іх рэалізацыі заключаецца ў наступным:

 inline bool operator==(const X lhs, const X rhs){  } inline bool operator!=(const X lhs, const X rhs){return !operator==(lhs,rhs);} inline bool operator< (const X lhs, const X rhs){  } inline bool operator> (const X lhs, const X rhs){return operator< (rhs,lhs);} inline bool operator<=(const X lhs, const X rhs){return !operator> (lhs,rhs);} inline bool operator>=(const X lhs, const X rhs){return !operator< (lhs,rhs);} 

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

Сінтаксіс перагрузкі пакінутых двайковых булева аператараў ( || , > ) варта правілах аператараў параўнання. Аднак вельмі малаверагодна, што вы знойдзеце разумны прэцэдэнт для гэтых 2.

1 Як і ва ўсіх эмпірычных правілах, часам могуць быць і прычыны разбіць гэтую. Калі гэта так, не забывайце, што левы аперанд двайковых аператараў параўнання, для якіх для функцый-членаў будзе *this , таксама павінен быць const . Такім чынам, аператар параўнання, рэалізаваны як функцыя-член, павінен мець гэтую подпіс:

 bool operator<(const X rhs) const {  } 

(Звярніце ўвагу на const ў канцы.)

2 Варта зазначыць, што ўбудаваная версія || і > выкарыстоўвае семантыку ярлыкоў. Хоць пэўныя карыстальнікам (паколькі яны з'яўляюцца сінтаксічных цукрам для выклікаў метадаў), не выкарыстоўвайце семантыку ярлыкоў. Карыстальнік будзе чакаць, што гэтыя аператары будуць мець семантыку цэтлікаў, і іх код можа залежыць ад яе. Таму настойліва рэкамендуецца НІКОЛІ не вызначаць іх.

арыфметычныя аператары

Унарные арыфметычныя аператары

Унарные аператары прырашчэння і декремента прысутнічаюць як у прэфіксе, так і ў постфиксном гусце. Каб распавесці адзін ад іншага, варыянты постфикса прымаюць дадатковы аргумент фіктыўнага int. Калі вы перагружаеце инкремент або декремент, абавязкова заўсёды выкарыстоўвайце як прэфікс, так і постфиксные версіі. Вось кананічная рэалізацыя прырашчэння, декремент варта тым жа правілах:

 class X { X operator++() { // do actual increment return *this; } X operator++(int) { X tmp(*this); operator++(); return tmp; } }; 

Звярніце ўвагу, што постфиксный варыянт рэалізаваны ў тэрмінах прэфікса. Таксама зьвярніце ўвагу, што postfix выконвае дадатковую копію. 2

Перагрузка унарный мінус і плюс не вельмі распаўсюджана і, верагодна, лепш за ўсё пазбегнуць. Пры неабходнасці яны, верагодна, павінны быць перагружаны як функцыі-члены.

2 Таксама зьвярніце ўвагу, што варыянт постфикса робіць больш працы і таму менш эфектыўны для выкарыстання, чым варыянт прэфікса. Гэта добрая прычына, як правіла, лепшае прырашчэнне прэфікса па прырашчэнню постфикса. Хоць кампілятары звычайна могуць аптымізаваць дадатковую працу паэтапнага прырашчэння для ўбудаваных тыпаў, яны, магчыма, ня змогуць зрабіць тое ж самае для карыстацкіх тыпаў (што можа быць нешта нявінна выглядаюць як итератор спісу). Пасля таго, як вы прывыклі рабіць i++ , вельмі складана не забыцца рабіць ++i замест таго, каб i ня мець убудаваны тып (плюс вам прыйдзецца мяняць код пры змене тыпу), таму лепш зрабіць звычку заўсёды выкарыстоўваць прырашчэнне прэфікса, калі postfix відавочна не патрабуецца.

Двайковыя арыфметычныя аператары

Для двайковых арыфметычных аператараў не забывайце падпарадкоўвацца трэцяй перагрузцы аператара асноўнага правілы: калі вы дае + , таксама падаеце += , калі вы дае - , не прапусціце -= і г.д. Кажуць, што Андрэй Кеніг быў першым, хто заўважыў, што складаныя агенты прысвойвання могуць выкарыстоўвацца ў якасці асновы для сваіх ня-складовых копій. Гэта значыць аператар + рэалізуецца ў тэрмінах += , - рэалізуецца ў тэрмінах -= і г.д.

У адпаведнасці з нашымі эмпірычнымі правіламі + і яго спадарожнікі павінны быць нечленами, у той час як іх складовыя супастаўлення ( += і г.д.), якія змяняюць іх левы аргумент, павінны быць членамі. Вось прыкладны код для += і + , іншыя двайковыя арыфметычныя аператары павінны быць рэалізаваны такім жа чынам:

 class X { X operator+=(const X rhs) { // actual addition of rhs to *this return *this; } }; inline X operator+(X lhs, const X rhs) { lhs += rhs; return lhs; } 

operator+= вяртае свой вынік за ссылку, а operator+ вяртае копію свайго выніку. Вядома, вяртанне спасылкі звычайна больш эфектыўны, чым вяртанне копіі, але ў выпадку operator+ няма спосабу капіявання. Калі вы пішаце a + b , вы чакаеце, што вынік будзе новым значэннем, таму operator+ павінен вярнуць новае значэнне. 3 Таксама зьвярніце ўвагу, што operator+ прымае свой левы аперанд копіяй, а не канстантнасцю спасылкай. Прычынай гэтага з'яўляецца тое ж, што і прычына, якая ўказвае, што для operator= прымаецца яго аргумент за копію.

Аператары маніпуляцыі з бітамі ~ > | ^ << >> павінны быць рэалізаваны так жа, як і арыфметычныя аператары. Аднак (за выключэньнем перагрузкі << і >> для вываду і ўводу) існуе вельмі мала разумных варыянтаў выкарыстання для перагрузкі.

3 Зноў жа, ўрок, які варта выняць з гэтага, складаецца ў тым, што a += b , як правіла, больш эфектыўны, чым a + b і павінен быць пераважным, калі гэта магчыма.

падлік масіва

Аператар індэкса масіва - гэта двайковы аператар, які павінен быць рэалізаваны як член класа. Ён выкарыстоўваецца для тыпаў кантэйнераў, якія дазваляюць атрымаць доступ да сваіх элементам дадзеных з дапамогай ключа. Кананічная форма іх забеспячэння такая:

 class X { value_type operator[](index_type idx); const value_type operator[](index_type idx) const; // ... }; 

Калі вы не хочаце, каб карыстальнікі вашага класа маглі змяняць элементы дадзеных, якiя вяртаюцца operator[] (у гэтым выпадку вы можаце апусціць неконстантный варыянт), вы заўсёды павінны прадастаўляць абодва варыянты аператара.

Калі вядома, што value_type спасылаецца на ўбудаваны тып, варыянт const аператара павінен вяртаць копію замест спасылкі const.

Аператары для ўказальных тыпаў

Для вызначэння ўласных итераторов або інтэлектуальных паказальнікаў вам неабходна перагрузіць унарный аператар разметкі прэфікса * і аператар доступу да сябра двайковага паказальніка infix -> :

 class my_ptr { value_type operator*(); const value_type operator*() const; value_type* operator->(); const value_type* operator->() const; }; 

Звярніце ўвагу, што для іх таксама амаль заўсёды спатрэбіцца як const, так і неконстантная версія. Для аператара -> , калі value_type мае тып class (або struct або union ), іншы operator->() выклікаецца рэкурсіўна, пакуль operator->() не вяртае значэнне тыпу non-class.

Унарный адрас-аператар ніколі не павінен перагружацца.

Для operator->*() см. Гэтае пытанне . Ён рэдка выкарыстоўваецца і, такім чынам, рэдка перагружаецца. Насамрэч нават итераторы ня перагружаюць яго.


Працягнуць Аператары пераўтварэнні

945
12 дек. адказ дадзены sbi 12 снеж. 2010-12-12 15:47 '10 у 15:47 2010-12-12 15:47

Тры асноўныя правілы перагрузкі аператара ў С ++

Калі справа даходзіць да перагрузкі аператара на З ++, ёсць тры асноўныя правілы, якім вы павінны прытрымлівацца. Як і ва ўсіх такіх правілах, сапраўды ёсць выключэнні. Часам людзі адхіляліся ад іх, і вынік быў нядрэнным кодам, але такіх станоўчых адхіленняў мала і далёка. Прынамсі, 99 з 100 такіх адхіленняў, якія я бачыў, былі неабгрунтаванымі. Тым не менш, гэта магло быць так жа, як і 999 з 1000. Такім чынам, вы лепш прытрымліваецеся наступных правілаў.

  • Кожны раз, калі сэнс аператара відавочна не зразумелы і бясспрэчны, яго не варта перагружаць. Замест гэтага падаецца функцыю з добра падабраным імем. У прынцыпе, першае і самае галоўнае правіла для перагрузак аператараў, у яго самым сэрцы, кажа: "Не рабі гэтага. Гэта можа здацца дзіўным, бо шмат што вядома аб перагрузцы аператара, і таму шмат якія артыкулы, кіраўніка кніг і іншыя тэксты датычацца ўсяго гэтага . Але, нягледзячы на ​​гэта, здавалася б, відавочныя доказы, ёсць толькі дзіўна мала выпадкаў, калі перагрузка аператара з'яўляецца падыходнай. Прычына ў тым, што на самой справе цяжка зразумець семантыку, якая ляжыць у аснове прымянення аператара, калі выкарыстанне аператара ў да Енё прыкладання не з'яўляецца агульнавядомым і бясспрэчным. Насуперак распаўсюджанаму меркаванню, гэта амаль ніколі не бывае.

  • Заўсёды прытрымвайцеся аператараў добра вядомай семантыкі.
    З ++ не стварае абмежаванняў па семантыцы перагружаных аператараў. Ваш кампілятар з радасцю прыме код, які рэалізуе двайковы аператар + , каб адняць яго правы аперанд. Аднак карыстальнікі такога аператара ніколі не будуць падазраваць выраз a + b для аднімання a з b . Вядома, гэта прадугледжвае, што семантыка аператара ў вобласці прыкладання бясспрэчная.

  • Заўсёды падаваць усе з набору звязаных аперацый.
    Аператары звязаны адзін з адным і з іншымі аперацыямі. Калі ваш тып падтрымлівае a + b , карыстальнікі чакаюць, што змогуць таксама выклікаць a += b . Калі ён падтрымлівае прырашчэнне прэфікса ++a , яны чакаюць, што a++ таксама будзе працаваць. Калі яны змогуць праверыць, ці ёсць a < b , яны напэўна будуць таксама мець магчымасць праверыць, ці ёсць a > b . Калі яны могуць капіяваць-пабудаваць ваш тып, яны чакаюць, што прызначэнне таксама будзе працаваць.

border=0

Працягнуць Рашэнне паміж членам і нечленом .

459
12 дек. адказ дадзены sbi 12 снеж. 2010-12-12 15:45 '10 а 15:45 2010-12-12 15:45

Агульны сінтаксіс перагрузкі аператара ў З ++

Вы не можаце змяніць значэнне аператараў для ўбудаваных тыпаў у З ++, аператары могуць быць перагружаны толькі для карыстацкіх тыпаў 1. Гэта значыць, па меншай меры, адзін з аперанд павінен быць пэўнага карыстальнікам тыпу. Як і ў выпадку з іншымі перагружанымі функцыямі, аператары могуць быць перагружаны для пэўнага набору параметраў толькі адзін раз.

Не ўсе аператары могуць быць перагружаны ў З ++. Сярод аператараў, якія не могуць быць перагружаны: . :: sizeof typeid .* І адзіны тернарный аператар у З ++, ?:

Сярод аператараў, якія могуць быць перагружаны ў С ++, наступныя:

  • арыфметычныя аператары: + - * / % і += -= *= /= %= (усе двайковыя инфикс); + - (унарный прэфікс); ++ -- (унарный прэфікс і постфикс)
  • біт-маніпуляцыя: > | ^ << >> і > |= ^= <<= >>= (усе двайковыя инфикс); ~ (Унарный прэфікс)
  • булева алгебра: == != < > <= >= || > (усе двайковыя инфикс); ! (Унарный прэфікс)
  • Кіраванне памяццю: new new[] delete delete[]
  • Аператары няяўна пераўтварэнні
  • збор: = [] -> ->* , (усе двайковыя инфикс); * > (Увесь унарный прэфікс) () (выклік функцыі, n-ary infix)

Аднак той факт, што вы можаце перагрузіць ўсё гэта, не азначае, што вы павiнны гэта рабіць. См. Асноўныя правілы перагрузкі аператара.

У З ++ аператары перагружаныя ў выглядзе функцый з адмысловымі імёнамі. Як і ў выпадку з іншымі функцыямі, перагружаныя аператары звычайна могуць быць рэалізаваны або як функцыя члена іх левага аперанда тыпу, альбо як не-членные функцыі. Незалежна ад таго, ці вы можаце выбраць ці выкарыстоўваць адзін з іх, залежыць ад некалькіх крытэраў. 2 Унарный аператар @ 3 які ўжываецца да аб'екту x, выклікаецца альбо як operator@(x) або як x.operator@() . Бінарны инфиксный аператар @ , які ўжываецца да аб'ектаў x і y , называецца альбо як operator@(x,y) , або як x.operator@(y) . 4

Аператары, якія рэалізаваны як функцыі, якія не з'яўляюцца членамі, часам з'яўляюцца сябрамі свайго тыпу аперанд.

1 Тэрмін "вызначаны карыстальнікам" можа трохі ўводзіць у зман. З ++ робіць адрозненне паміж ўбудаванымі тыпамі і пэўнымі карыстальнікам тыпамі. Да першага ставяцца, напрыклад, int, char і double; да апошніх адносяцца ўсе тыпы структуры, класа, аб'яднанні і пералічэння, у тым ліку са стандартнай бібліятэкі, нават калі яны не з'яўляюцца, як такія, пэўнымі карыстальнікамі.

2 Гэта апісана ў больш позняй часткі гэтага FAQ.

3 @ не з'яўляецца дапушчальным аператарам у З ++, таму я выкарыстоўваю яго як запаўняльнік.

4 Адзіны тернарный аператар у С ++ не можа быць перагружаны, і адзіны аператар n-ary заўсёды павінен быць рэалізаваны як функцыя-член.


Працягнуць Тры асноўных правілы перагрузкі аператара на С ++ .

242
12 дек. адказ дадзены sbi 12 снеж. 2010-12-12 15:46 '10 ў 15:46 2010-12-12 15:46

Рашэнне паміж членам і нечленом

Бінарныя аператары = (прысваенне), [] (падпіска на масівы), -> (доступ членаў), а таксама аператар n-ary () (выклік функцыі) заўсёды павінны быць рэалізаваны як функцыі-члены, таму што для іх патрабуецца сінтаксіс мовы .

Іншыя аператары могуць быць рэалізаваны або як члены, альбо як нечлены. Некаторыя з іх, аднак, звычайна павінны быць рэалізаваны як функцыі, якія не з'яўляюцца членамі, таму што іх левы аперанд не можа быць зменены вамі. Найбольш прыкметнымі з іх з'яўляюцца аператары ўводу і вываду << і >> , левыя аперанды якіх з'яўляюцца класамі патокаў з стандартнай бібліятэкі, якія занадта змяніць.

Для ўсіх аператараў, дзе вам трэба альбо рэалізаваць іх як функцыю-член, альбо не-член функцыю, выкарыстоўваць наступныя правілы вялікага пальца:

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