Kokios yra pagrindinės operatoriaus perkrovos taisyklės ir idėjos?

Pastaba Atsakymai buvo pateikti konkrečia tvarka, tačiau kadangi daugelis vartotojų surūšiuoti atsakymus pagal balsus, o ne laiką, kurį jie davė, čia yra atsakymų rodyklė tokia tvarka, kokia jie yra prasmingiausi:

<sub> (Pastaba. Tai reiškia, kad įvedate dažniausiai užduodamus klausimus apie C ++ stekų perpildymą . Jei norite kritikuoti idėją pateikti DUK šioje formoje, tada skelbimas apie tai prasidėjo . Atsakymai į šį klausimą yra stebimi C + + pokalbyje , kur pirmą kartą prasidėjo DUK idėja, todėl jūsų atsakymą greičiausiai skaitys tie, kurie atėjo su šia idėja.)

1936 m
12 дек. nustatytas sbi 12 dec. 2010-12-12 15:44 '10, 15:44, 2010-12-12 15:44
@ 7 atsakymai

Pagrindiniai operatoriai perkrovimui

Didžioji dalis perkrovos operatorių yra katilo kambario kodas. Tai nenuostabu, nes operatoriai yra tiesiog sintaksinis cukrus, jų faktinį darbą galima atlikti (ir dažnai nukreipti) į paprastas funkcijas. Tačiau svarbu gauti katilo kodą. Jei nepavyks, jūsų operatoriaus kodas nebus kompiliuojamas, arba vartotojo kodas nebus kompiliuojamas, arba vartotojo kodas atrodys stebėtinai.

Priskyrimo operatorius

Apie paskyrimą galima pasakyti daug. Tačiau dauguma jų jau buvo paminėti garsiojoje „Mop-and-Swap DUK“ , todėl aš čia praleisiu didžiąją dalį, tiesiog nurodant idealų priskyrimo operatorių nuorodai:

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

„Bitshift“ operatoriai (naudojami „Stream I / O“)

Bitų perjungimo operatoriai << ir >> , nors jie vis dar naudojami aparatūros sąsajoje bitų manipuliavimo funkcijoms, kurias jie paveldėjo iš C, dažniau pasitaiko kaip perkrautas įvesties ir išvesties srauto operatoriai daugumoje programų. Jei norite perkrauti instrukcijas kaip bitų manipuliavimo operatorius, žr. Toliau pateiktą skyrių apie dvejetainius aritmetinius veiksmus. Jei norite įgyvendinti savo pasirinktą formatą ir analizuoti logiką, kai objektas naudojamas su „iostreams“, tęskite.

Srautų operatoriai, tarp dažniausiai perkraunamų operatorių, yra dvejetainiai infiksavimo operatoriai, kurių sintaksė nerodo apribojimų, ar jie turėtų būti nariai, ar ne nariai. Kadangi jie keičia savo kairįjį argumentą (jie keičia siūlų būseną), jie pagal nykščio taisykles turi būti įgyvendinami kaip jų kairiųjų operandų tipo nariai. Tačiau jų kairieji operandai yra srautai iš standartinės bibliotekos, ir nors dauguma standartinės bibliotekos apibrėžtų srautų išvesties ir įvesties operatorių iš tiesų yra apibrėžiami kaip siūlų klasių nariai, kai diegiate išvesties ir įvesties operacijas savo tipams, negalite keisti standartinių bibliotekų srautų tipų . Štai kodėl jums reikia įdiegti šiuos operatorius savo tipams kaip ne narių funkcijas. Šių dviejų kanoninių formų yra:

 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; } 

Įdiegus operator>> rankiniu būdu, sriegių būsenos nustatymas yra būtinas tik tada, kai atliekamas pats skaitymas, tačiau rezultatas neatitinka tikėtino.

Funkcijų skambučio operatorius

Funkcinių skambučių operatorius, naudojamas funkciniams objektams kurti, taip pat žinomas kaip „funktorai“, turi būti apibrėžiamas kaip nario funkcija, todėl visada turi šį netiesioginį narių funkcijų argumentą. Be to, galima perkrauti, kad priimtų bet kokį papildomą argumentą, įskaitant nulį.

Čia yra sintaksės pavyzdys:

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

Naudoti:

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

Standartinėje C ++ bibliotekoje objektų objektai visada nukopijuojami. Todėl jūsų funkcijų objektai turėtų būti pigūs kopijuoti. Jei yra būtina, kad funkcijai būtų naudojami brangu kopijuoti duomenys, geriau saugoti šiuos duomenis kitur ir kreiptis į funkcijų objektą.

Palyginimo operatoriai

Dvejetainiai infiksų palyginimo operatoriai, vadovaudamiesi nykščio taisyklėmis, turėtų būti įgyvendinami kaip ne narių funkcijos 1 . Unitary prefix negation ! (pagal tas pačias taisykles) turi būti įgyvendinama kaip nario funkcija. (tačiau paprastai nerekomenduojama ją perkrauti.)

Standartiniai bibliotekos algoritmai (pvz., std::sort() ) ir tipai (pvz., std::map ) visada tikisi operator< . Tačiau jūsų tipo naudotojai tikisi, kad visi kiti operatoriai taip pat dalyvaus, todėl, jei apibrėžsite operator< , būtinai vadovaukitės trečia pagrindine operatoriaus perkrovos taisykle ir nustatykite visus kitus loginius palyginimo operatorius. Kanoninis būdas juos įgyvendinti yra toks:

 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);} 

Svarbu pažymėti, kad tik du iš šių operatorių iš tikrųjų kažką daro, kiti tiesiog nukreipia savo argumentus į vieną iš šių dviejų, kad atliktų faktinį darbą.

Likusių dvejetainių loginių operatorių ( || , > ) perkrovimo sintaksė atitinka palyginimo operatorių taisykles. Tačiau labai mažai tikėtina, kad rasite protingą precedentą 2 .

1 Kaip ir visoms nykščio taisyklėms, kartais gali būti priežastys nutraukti šią. Jei taip yra, nepamirškite, kad kairiųjų dvejetainių palyginimo operatorių operandų, kurių nario funkcijoms yra *this , taip pat turi būti const . Taigi palyginimo operatorius, įgyvendinamas kaip nario funkcija, turi turėti šį parašą:

 bool operator<(const X rhs) const {  } 

(Atkreipkite dėmesį į galą pabaigoje.)

2 Pažymėtina, kad įdėta versija || ir > naudoja etiketės semantiką. Nors naudotojo apibrėžta (nes jie yra sintaksinis cukrus metodiniams skambučiams), nenaudokite etikečių semantikos. Vartotojas tikisi, kad šie operatoriai turės etiketės semantiką, ir jų kodas gali priklausyti nuo jo. Todėl primygtinai rekomenduojama NIEKADA nustatyti juos.

Aritmetiniai operatoriai

Unary aritmetiniai operatoriai

Tiek prefikso, tiek ir postfix skonio metu yra unikalūs prieaugio ir sumažinimo operatoriai. Norėdami pasakyti vieną iš kitų, postfix variantai pasirinktinai nurodo manekeno int. Jei esate perkrovos prieaugis ar sumažėjimas, būtinai naudokite tiek prefikso, tiek ir postfix versijas. Čia yra kanoninis prieaugio įgyvendinimas, sumažinimas atitinka tas pačias taisykles:

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

Atkreipkite dėmesį, kad postfix variantas įgyvendinamas pagal prefiksą. Taip pat atkreipkite dėmesį, kad postfix atlieka papildomą kopiją. 2

Neperspektyvus neoficialus minuso ir pliuso perkrovimas ir tikriausiai geriausia vengti. Jei reikia, jie tikriausiai turėtų būti perkrauti kaip narių funkcijos.

2 Taip pat atkreipkite dėmesį, kad postfix parinktis veikia daugiau ir todėl yra mažiau efektyvi nei prefikso versija. Tai yra gera priežastis, kaip taisyklė, ji pirmenybę didina didindama postfiksą. Nors kompiliatoriai paprastai gali optimizuoti papildomus papildomus darbus, skirtus įmontuotiems tipams, jie gali nesugebėti padaryti tą patį pagal pasirinktinius tipus (kurie gali būti kažkas nekaltų, atrodytų kaip sąrašo iteratorius). Įpratę daryti „ i++ , labai sunku nepamiršti padaryti „ ++i o ne i įmontuoto tipo (taip pat turėsite pakeisti kodą keičiant tipą), todėl geriausia, jei norite, kad visada būtų naudojamas prefiksų prieaugis, jei „postfix“ akivaizdžiai nėra reikalaujama.

Dvejetainiai aritmetiniai operatoriai

Binariniams aritmetiniams operatoriams nepamirškite paklusti trečiajam pagrindinės taisyklės operatoriaus perkrovimui: jei pateikiate + , taip pat nurodykite += , jei pateikiate - , nepraleiskite -= ir tt Sakoma, kad Andrejus Koenigas pirmą kartą pastebėjo, kad sudėtiniai priskyrimo agentai gali būti naudojami kaip jų nesudėtinių kopijų pagrindas. Tai reiškia, kad + operatorius įgyvendinamas pagal += , - įgyvendinamas pagal -= ir tt

Pagal mūsų nykščio taisykles, + ir jos palydovai turi būti ne nariai, o jų sudėtiniai palyginimai ( += ir tt), kurie keičia jų kairįjį argumentą, turi būti nariai. Čia pateikiamas += ir + kodo pavyzdys, kiti binariniai aritmetiniai operatoriai turėtų būti įgyvendinami tokiu pačiu būdu:

 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+= grąžina rezultatą pagal nuorodą, o operator+ grąžina rezultato kopiją. Žinoma, nuorodos grąžinimas paprastai yra efektyvesnis nei kopijos grąžinimas, tačiau operator+ atveju jo kopijuoti negalima. Rašydami a + b , tikitės, kad rezultatas bus nauja vertė, todėl operator+ turėtų grąžinti naują vertę. 3 Taip pat atkreipkite dėmesį, kad operator+ priima kairįjį operandą kaip kopiją , o ne kaip nuolatinę nuorodą. To priežastis yra ta pati priežastis, kuri nurodo, kad operator= jo argumentas laikomas kopija.

„Bit“ manipuliavimo operatoriai ~ > | ^ << >> turėtų būti įgyvendinami taip pat, kaip aritmetiniai operatoriai. Vis dėlto (išskyrus perkrovimą << ir >> išvesties ir įvesties atveju) yra labai nedaug pagrįstų panaudojimų perkrovimui.

3 Vėlgi, pamoką, kurią reikia išmokti, yra tai, kad a += b paprastai yra efektyvesnis už a + b ir, jei įmanoma, turėtų būti pageidaujamas.

„Array“ skaičiavimas

Masyvo indekso operatorius yra dvejetainis operatorius, kuris turi būti įgyvendintas kaip klasės narys. Jis naudojamas konteinerių tipams, leidžiantiems prieiti prie duomenų elementų su raktu. Kanoninė jų teikimo forma yra tokia:

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

Jei nenorite, kad jūsų klasės naudotojai keistų operator[] grąžintus duomenų elementus (šiuo atveju galite praleisti ne nuolatinę parinktį), visada turėtumėte pateikti abi operatoriaus versijas.

Jei yra žinoma, kad value_type reiškia integruotą tipą, operatoriaus const variantas turėtų grąžinti kopiją vietoj const nuorodos.

Rodyklės tipų operatoriai

Jei norite nustatyti savo iteratorius ar išmaniuosius rodiklius, turite perkrauti unary prefix žymėjimo operatorių * ir prieigos operatorių į dvejetainį rodyklės narį infix -> :

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

Atkreipkite dėmesį, kad jie taip pat beveik visada reikalauja ir konstantos, ir ne nuolatinės versijos. Operatoriui -> , jei value_type yra tipo class (arba struct arba union ), kitas operator->() yra vadinamas rekursiniu, o operator->() negrąžina ne klasės vertės.

Unary address operatorius niekada neturėtų būti perkrautas.

operator->*() žr. Šį klausimą . Jis retai naudojamas ir todėl retai perkrautas. Tiesą sakant, net iteratoriai neperkrauna.


Tęsti konversijos operatorius

945
12 дек. atsakymas pateikiamas sbi 12 dec. 2010-12-12 15:47 '10, 15:47, 2010-12-12 15:47

Trys pagrindinės operatoriaus perkrovos C ++ taisyklės

Kai kalbama apie operatoriaus perkrovimą „C ++“, turite tris pagrindines taisykles, kurias turėtumėte laikytis . Kaip ir visose tokiose taisyklėse, iš tikrųjų yra išimčių. Kartais žmonės nuo jų nukrypo, o rezultatas buvo geras kodas, tačiau tokie teigiami nuokrypiai yra nedideli ir toli. Mažiausiai 99 iš 100 tokių pastebimų sutrikimų buvo nepagrįsti. Tačiau jis gali būti toks pat kaip 999 iš 1000. Taigi, geriau laikykitės šių taisyklių.

  • Kai operatoriaus reikšmė yra neaiški ir neabejotina, ji neturėtų būti perkrauta. Vietoj to, pateikite funkciją su gerai parinktu pavadinimu. Iš esmės, pirmoji ir svarbiausia taisyklė dėl operatoriaus perkrovos, savo širdyje, sako: „Nedarykite to. Tai gali atrodyti keista, nes daug žinoma apie operatoriaus perkrovą, todėl daugelis straipsnių, knygų skyrių ir kiti tekstai yra susiję su visa tai. Tačiau, nepaisant akivaizdžiai akivaizdžių įrodymų, yra tik stebėtinai nedaug atvejų, kai operatoriaus perkrovimas yra tinkamas, todėl yra sunku suprasti operatoriaus paraiškos pagrindą, jei operatorius naudojasi eno paraiška nėra gerai žinomas ir neginčijamas. Priešingai populiarių įsitikinimų, tai beveik niekada atvejis.

  • Visada laikykitės gerai žinomų semantikos operatorių.
    C ++ nesukuria apribojimų perkrautų operatorių semantikai. Jūsų kompiliatorius mielai priims kodą, įgyvendinantį dvejetainį + operatorių, atimdamas jo dešinįjį operandą. Tačiau tokio operatoriaus naudotojai niekada neturės įtarimų, kad išraiška a + b atimti iš b . Žinoma, tai reiškia, kad operatoriaus semantika taikymo srityje yra neginčijama.

  • Visada pateikite visas susijusias operacijas.
    Operatoriai yra susiję vienas su kitu ir su kitomis operacijomis. Jei jūsų tipas palaiko a + b , vartotojai tikisi, kad jie taip pat gali skambinti a += b . Jei jis palaiko didinimą „prefix“ ++a , jie tikisi, a++ veiks taip pat. Jei jie gali patikrinti, ar yra a < b , jie tikriausiai galės patikrinti, ar yra a > b . Jei jie gali kopijuoti savo tipą, jie tikisi, kad užduotis bus atlikta taip pat.

border=0

Tęsti sprendimą tarp nario ir nario .

459
12 дек. atsakymas pateikiamas sbi 12 dec. 2010-12-12 15:45 '10, 15:45, 2010-12-12 15:45

Bendra C ++ operatoriaus perkrovos sintaksė

Jūs negalite keisti operatorių vertės integruotiems tipams „C ++“, operatoriai gali būti perkrauti tik individualiems tipams 1 . Tai reiškia, kad bent vienas iš operandų turi būti vartotojo apibrėžto tipo. Kaip ir kitoms perkrautoms funkcijoms, operatoriai gali būti perkrauti tam tikram parametrų rinkiniui tik vieną kartą.

Ne visi operatoriai gali būti perkrauti C + +. Tarp operatorių, kurių negalima perkrauti :. :: sizeof typeid .* ir vienintelis typeid operatorius C ++, ?:

Tarp operatorių, kurie gali būti perkrauti „C ++“, yra šie:

  • aritmetiniai operatoriai: + - * / % ir += -= *= /= %= (visi dvejetainiai infiksai); + - (vieningas prefiksas); ++ -- (unary prefix ir postfix)
  • bitų manipuliavimas: > | ^ << >> ir > |= ^= <<= >>= (visi dvejetainiai infiksai); ~ (unary prefix)
  • Būlio algebra: == != < > <= >= || > (visi dvejetainiai infiksai); ! (unary prefix)
  • Atminties valdymas: new new[] delete delete[]
  • Netiesioginiai konversijos operatoriai
  • kolekcija: = [] -> ->* , (visi dvejetainiai infiksai); * > (visi unary prefix) () (funkcijos skambutis, n-ary infix)

Tačiau tai, kad galite visa tai perkrauti, nereiškia, kad turite tai padaryti. Žr. Pagrindinės operatoriaus perkrovos taisyklės.

„C ++“ operatoriai yra perkrauti kaip funkcijos su specialiais pavadinimais . Kaip ir kitų funkcijų atveju, perkrovę operatoriai paprastai gali būti įgyvendinami arba kaip kairiojo tipo operacijos nario funkcija , arba kaip ne narių funkcijos . Nepriklausomai nuo to, ar galite pasirinkti, ar naudoti vieną iš jų, priklauso nuo kelių kriterijų. 2 Unary operatorius @ 3, pritaikytas objektui x, vadinamas operator@(x) arba x.operator@() . Dvejetainis infiksavimo operatorius @ , taikomas objektams x ir y , vadinamas operator@(x,y) arba x.operator@(y) . 4

Operatoriai, kurie yra vykdomi kaip narystės funkcijos, kartais yra jų operando tipo draugai.

1 Sąvoka „vartotojo apibrėžta“ gali būti šiek tiek klaidinanti. „C ++“ skiriasi nuo integruotų tipų ir naudotojo nustatytų tipų. Pirmasis apima, pavyzdžiui, int, char ir double; pastarasis apima visų tipų struktūrą, klasę, sąjungą ir skaičiavimus, įskaitant iš standartinės bibliotekos, net jei jie tokiu būdu nėra apibrėžti naudotojų.

2 Tai aprašyta vėlesnėje šio DUK dalyje.

3 @ nėra galiojantis C + + operatorius, todėl naudoju jį kaip vietos žymeklį.

4 Vieno trikampio operatorius C ++ sistemoje negali būti perkrautas, o vienas n-ary operatorius visada turi būti įgyvendinamas kaip nario funkcija.


Tęsti Trys pagrindinės operatoriaus perkrovos C ++ taisyklės .

242
12 дек. atsakymas pateikiamas sbi 12 dec. 2010-12-12 15:46 '10, 15:46, 2010-12-12 15:46

Sprendimas tarp nario ir ne nario

Dvejetainiai operatoriai = (priskyrimas), [] (prenumerata masyvams), -> (nario prieiga) ir n-ary () operatorius (funkcijų skambutis) visada turėtų būti įgyvendinami kaip narių funkcijos , nes jiems reikalinga kalbos sintaksė .

Kiti operatoriai gali būti įgyvendinami kaip nariai arba ne nariai. Tačiau kai kurie iš jų paprastai turi būti vykdomi kaip narystės funkcijos, nes jūs negalite pakeisti savo kairiojo operando. Didžiausias iš jų yra įvesties ir išvesties operatoriai << ir >> , kurių kairieji operandai yra srautų klasės iš standartinės bibliotekos, kurių negalite keisti.

Для всех операторов, где вам нужно либо реализовать их как функцию-член, либо не-членную функцию, использовать следующие правила большого пальца :

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