Koja su osnovna pravila i idiomi za preopterećenje operatora?

Napomena. Odgovori su dati u određenom redoslijedu, ali budući da mnogi korisnici sortiraju odgovore prema glasovima, a ne u vremenu koje su dali, ovdje je indeks odgovora u redoslijedu kojim su najviše razumljivi:

<sub> (Napomena. To znači da unosite često postavljana pitanja o C ++ preljevu stogova . Ako želite kritizirati ideju pružanja FAQ-a u ovom obliku, onda bi postavljanje na meta koji je pokrenulo sve to bilo mjesto za to. Odgovori na ovo pitanje prate se u C ++ chatu , gdje je ideja o FAQ-u počela na prvom mjestu, tako da će vaš odgovor najvjerojatnije pročitati oni koji su došli na ovu ideju.)

1936
12 дек. set sbi 12 dec. 2010-12-12 15:44 '10 u 15:44 2010-12-12 15:44
@ 7 odgovora

Opći operatori za pretovar

Najveći dio posla preopterećenja je kod kotlovnice. To ne iznenađuje, budući da su operatori jednostavno sintaktički šećer, njihov stvarni rad može se izvesti (i često usmjeravati) na jednostavne funkcije. Ali važno je da dobijete kod kotla. Ako ne uspijete, ili se vaš operacijski kod neće prevesti, ili se korisnički kod neće prevesti, ili će korisnički kod izgledati iznenađujuće.

Operator dodjele

Mnogo se toga može reći o imenovanju. Međutim, većina njih već je spomenuta u poznatom "Mop-and-Swap FAQ" , pa ću preskočiti većinu ovoga ovdje, samo navodeći idealnog operatora dodjele za vezu:

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

Bitshift operatori (koji se koriste za Stream I / O)

Operatori pomaka bitova << i >> , iako se još uvijek koriste u hardverskom sučelju za funkcije manipulacije bitovima koje nasljeđuju od C, postali su uobičajeniji kao preopterećeni operatori ulaznog i izlaznog toka u većini aplikacija. Za preopterećenje naredbi kao operatora manipulacije bitom, pogledajte odjeljak ispod o binarnim aritmetičkim operacijama. Da biste implementirali vlastiti prilagođeni format i analizirali logiku kada se vaš objekt koristi s iostreams, nastavite.

Operatori struje, među najčešće korištenim operatorima, su binarni infix operatori, za koje sintaksa ne označava ograničenja o tome trebaju li biti članovi ili nečlanovi. Budući da mijenjaju svoj lijevi argument (mijenjaju stanje niti), moraju, u skladu s pravilima palca, biti implementirani kao članovi svoje vrste lijevih operanada. Međutim, njihovi lijevi operandi su tokovi iz standardne knjižnice, i iako je većina stream izlaznih i ulaznih operatora definiranih standardnom knjižnicom doista definirana kao članovi klasa niti, kada implementirate izlazne i ulazne operacije za svoje vlastite tipove, ne možete promijeniti standardne tipove knjižničnih tokova , Zato trebate implementirati ove operatore za svoje vlastite vrste kao nečlanske funkcije. Kanonski oblici ovih dviju su:

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

Kada ručno provodite operator>> , postavljanje stanja niti je potrebno samo kada je učinjeno samo čitanje, ali rezultat se ne podudara s očekivanim.

Funkcija Poziv operateru

Operator poziva funkcije koji se koristi za kreiranje funkcionalnih objekata, također poznat kao funktori, mora biti definiran kao funkcija člana , tako da uvijek ima implicitan this argument o članskim funkcijama. Osim toga, može se preopteretiti da prihvati bilo koji broj dodatnih argumenata, uključujući nulu.

Evo primjera sintakse:

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

Upotreba:

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

U standardnoj C ++ biblioteci objektni objekti se uvijek kopiraju. Stoga bi vaši objekti funkcije trebali biti jeftini za kopiranje. Ako je objekt apsolutno neophodan za korištenje podataka koji su skupi za kopiranje, bolje je pohraniti te podatke na drugom mjestu i uputiti se na objekt funkcije.

Operatori usporedbe

Operatori za usporedbu binarnih infixa trebali bi, prema pravilima palca, biti implementirani kao nečlanske funkcije 1 . Jedinstveno negiranje prefiksa ! treba (prema istim pravilima) primijeniti kao člansku funkciju. (ali u pravilu se ne preporuča preopterećenje.)

Standardni knjižnični algoritmi (na primjer, std::sort() ) i tipovi (na primjer, std::map ) uvijek će očekivati operator< . Međutim, korisnici vašeg tipa očekuju da će i svi ostali operatori biti prisutni, pa ako definirate operator< , svakako slijedite treće osnovno pravilo preopterećenja operatora, a također odredite i sve ostale logičke operatore usporedbe. Kanonski način njihove provedbe je sljedeći:

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

Važno je napomenuti da samo dva od tih operatera zapravo nešto poduzimaju, drugi jednostavno preusmjeravaju svoje argumente ni na jednu od ove dvije da bi obavili stvarni posao.

Sintaksa za preopterećenje preostalih binarnih boole operatora ( || , > ) slijedi pravila operatora usporedbe. Međutim, vrlo je malo vjerojatno da ćete za njih pronaći razumni presedan.

Kao i kod svih pravila palca, ponekad mogu postojati razlozi da se ovo prekine. Ako je to slučaj, nemojte zaboraviti da lijevi operand operatora binarnih usporedbi, za koji je za funkcije člana *this , također mora biti const . Dakle, operator usporedbe, implementiran kao funkcija člana, mora imati ovaj potpis:

 bool operator<(const X rhs) const {  } 

(Zabilježite const na kraju.)

2 Treba napomenuti da ugrađena verzija || i > koristi semantiku oznaka. Iako je definiran od strane korisnika (kao što su sintaktički šećer za pozive metoda), nemojte koristiti semantiku oznaka. Korisnik će očekivati ​​da ovi operatori imaju semantiku oznaka, a njihov kôd može ovisiti o njemu. Stoga se preporuča da ih NIKADA ne identifikujete.

Aritmetički operatori

Jedinstveni aritmetički operatori

Jedinstveni operatori inkrementa i dekrementa prisutni su u prefiksu i postfixu. Da bismo rekli jedno od drugog, varijante postfixa uzimaju neobavezni argument dummy int. Ako preopterećujete prirast ili smanjenje, obavezno uvijek koristite i prefiks i postfix verzije. Ovo je kanonska implementacija inkrementa, a smanjenje slijedi ista pravila:

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

Imajte na umu da je postfix varijanta implementirana u smislu prefiksa. Također imajte na umu da postfix izvodi dodatnu kopiju. 2

Preopterećenje unarnog minusa i plusa nije uobičajeno i vjerojatno je najbolje izbjegavati. Ako je potrebno, vjerojatno bi trebali biti preopterećeni kao članske funkcije.

2 Također imajte na umu da postfix opcija čini više rada i stoga je manje učinkovita za korištenje od prefiksne verzije. To je dobar razlog, u pravilu, preferira povećanje prefiksa povećavanjem postfixa. Iako kompilatori obično mogu optimizirati dodatni inkrementalni rad za ugrađene tipove, oni možda neće moći učiniti isto za prilagođene vrste (što može biti nešto nevino koje izgleda kao iterator popisa). Nakon što ste navikli raditi i++ , vrlo je teško ne zaboraviti napraviti ++i umjesto da nemam ugrađeni tip (plus morat ćete promijeniti kod kod promjene tipa), tako da je najbolje napraviti naviku uvijek koristiti prefiks ako postfix nije jasno potreban.

Binarni aritmetički operatori

Za binarne aritmetičke operatore, ne zaboravite slijediti treće preopterećenje operatora glavnog pravila: ako ste dali + , također navedite += , ako ste dali - , ne propustite -= , itd. Kaže se da je Andrei Koenig bio prvi koji je primijetio da se složeni agenti za dodjelu mogu koristiti kao osnova za njihove nekompozitne kopije. To jest, operator + je implementiran u smislu += , - implementiran je u smislu -= , itd.

U skladu s našim pravilima palca, + i njegovi sateliti moraju biti nečlanovi, dok njihove složene usporedbe ( += , itd.), Koje mijenjaju svoj lijevi argument, moraju biti članovi. Evo primjera koda za += i + , druge binarne aritmetičke operatore treba implementirati na isti način:

 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+= vraća svoj rezultat referencom, a operator+ vraća kopiju rezultata. Naravno, vraćanje veze obično je učinkovitije od vraćanja kopije, ali u slučaju operator+ nema načina da se kopira. Kada napišete a + b , očekujete da će rezultat biti nova vrijednost, pa operator+ treba vratiti novu vrijednost. 3 Također imajte na umu da operator+ prihvaća svoj lijevi operand kao kopiju , a ne kao konstantnu referencu. Razlog za to je isti kao i razlog koji ukazuje da se za operator= njegov argument uzima kao kopija.

Operatori manipulacije bitovima ~ > | ^ << >> treba implementirati na isti način kao aritmetički operatori. Međutim (s iznimkom preopterećenja << i >> za izlaz i ulaz) postoji vrlo malo razumne namjene za preopterećenje.

Opet, iz ovoga se može izvući pouka da je a += b obično učinkovitija od a + b i da je poželjna ako je moguće.

Brojanje nizova

Indeks operatora niza je binarni operator koji mora biti implementiran kao član klase. Koristi se za tipove spremnika koji omogućuju pristup elementima podataka ključem. Kanonski oblik njihove odredbe je sljedeći:

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

Ako ne želite da korisnici vaše klase mijenjaju elemente podataka koje vraća operator[] (u ovom slučaju možete izostaviti opciju koja nije stalna), uvijek biste trebali dati obje verzije operatora.

Ako je poznato da se vrijednost_tip odnosi na ugrađeni tip, const varijanta operatora treba vratiti kopiju umjesto const reference.

Operatori za tipove pokazivača

Da biste definirali vlastite iteratore ili pametne pokazivače, morate preopteretiti unary operater označavanja prefiksa * i operatora pristupa binarnom infixu člana člana kazaljke -> :

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

Imajte na umu da oni također gotovo uvijek zahtijevaju i const i non-constant verziju. Za operatora -> , ako je vrijednost_tip class tipa (ili struct ili union ), drugi operator->() se naziva rekurzivno, dok operator->() ne vraća vrijednost koja nije klasa.

Unar-adresar nikad ne bi trebao biti preopterećen.

Za operator->*() pogledajte ovo pitanje . Rijetko se koristi i stoga se rijetko preopterećuje. Zapravo, čak ga iteratori ne preopterećuju.


Nastavi operatore konverzije

945
12 дек. odgovor je dan sbi 12 dec. 2010-12-12 15:47 '10 u 15:47 2010-12-12 15:47

Tri osnovna pravila za preopterećenje operatora u C ++

Kada je riječ o preopterećenju operatora u C ++, postoje tri osnovna pravila koja trebate slijediti . Kao i kod svih takvih pravila, doista postoje iznimke. Ponekad su ljudi odstupali od njih, a rezultat je bio dobar kod, ali takva su pozitivna odstupanja malo i daleko. Najmanje 99 od 100 takvih abnormalnosti koje sam vidio bila su neutemeljena. Međutim, može biti isto kao 999 od 1000. Dakle, bolje slijedite sljedeća pravila.

  • Kad god je značenje operatera očito nejasno i neporecivo, ono se ne smije preopterećivati. Umjesto toga, omogućite funkciju s dobro odabranim imenom. U načelu, prvo i najvažnije pravilo za preopterećenja operatera, u samom njegovom srcu, kaže: “Nemojte to činiti. Ovo može izgledati čudno jer se mnogo zna o preopterećenosti operatora, pa se mnogi članci, poglavlja knjiga i drugi tekstovi odnose na sve to. No, unatoč ovom naizgled očiglednom dokazu, postoji samo neočekivano malo slučajeva u kojima je preopterećenje operatora prikladno. Razlog je u tome što je zapravo teško razumjeti semantiku na kojoj se temelji aplikacija operatora ako operater koristi en zahtjev nije poznato i neosporno. Suprotno uvriježenom mišljenju, to je gotovo nikad nije slučaj.

  • Uvijek se pridržavajte poznatih operatora semantike.
    C ++ ne stvara ograničenja semantike preopterećenih operatora. Vaš će kompajler rado prihvatiti kod koji implementira binarni + operater za oduzimanje njegovog desnog operanda. Međutim, korisnici takvog operatora nikada neće sumnjati da izraz a + b oduzima a od b . Naravno, to pretpostavlja da je semantika operatora u domeni primjene neporeciva.

  • Uvijek pružite sve skupove povezanih operacija.
    Operateri su povezani jedni s drugima i s drugim operacijama. Ako vaš tip podržava a + b , korisnici očekuju da mogu nazvati a += b . Ako podupire povećanje ++a prefiksa, očekuju da će a++ raditi. Ako mogu provjeriti postoji a < b , vjerojatno će također moći provjeriti postoji a > b . Ako mogu kopirati svoj tip, očekuju da će i zadatak raditi.

border=0

Nastaviti odluku između člana i nečlanice .

459
12 дек. odgovor je dan sbi 12 dec. 2010-12-12 15:45 '10 u 15:45 2010-12-12 15:45

Opća sintaksa za preopterećenje operatora u C ++

Ne možete promijeniti vrijednost operatora za ugrađene tipove u C ++, operatori mogu biti preopterećeni samo za prilagođene vrste 1 . To jest, najmanje jedan od operanada mora biti korisnički definiranog tipa. Kao i kod drugih preopterećenih funkcija, operatori mogu biti preopterećeni za određeni skup parametara samo jednom.

Nisu svi operatori preopterećeni u C ++. Među operatorima koji ne mogu biti preopterećeni :. :: sizeof typeid .* i jedini ternarni operator u C ++, ?:

Među operatorima koji mogu biti preopterećeni u C ++ su sljedeći:

  • aritmetički operatori: + - * / % i += -= *= /= %= (svi binarni infix); + - (jedinstveni prefiks); ++ -- (jedinstveni prefiks i postfix)
  • manipulacija bitovima: > | ^ << >> i > |= ^= <<= >>= (svi binarni infix); ~ (unarni prefiks)
  • Booleova algebra: == != < > <= >= || > (sve binarne infix); ! (unarnički prefiks)
  • Upravljanje memorijom: new new[] delete delete[]
  • Operatori implicitnih pretvorbi
  • zbirka: = [] -> ->* , (sve binarne infix); * > (sve unarni prefiks) () (poziv funkcije, n-ary infix)

Međutim, činjenica da sve to možete preopteretiti ne znači da to morate učiniti. Pogledajte Osnovna pravila za preopterećenje operatora.

U C ++, operatori su preopterećeni kao funkcije s posebnim imenima . Kao iu slučaju drugih funkcija, preopterećeni operatori se obično mogu implementirati ili kao funkcija člana njihovog lijevog operanda tipa , ili kao nečlanske funkcije . Bez obzira možete li izabrati ili koristiti neku od njih, ovisi o nekoliko kriterija. 2 Unarni operator @ 3 koji se primjenjuje na objekt x naziva se ili kao operator@(x) ili kao x.operator@() . Binarni infix operator @ , primijenjen na objekte x i y , naziva se ili kao operator@(x,y) ili x.operator@(y) . 4

Operatori koji se implementiraju kao nečlanske funkcije ponekad su prijatelji svojeg tipa operanda.

1 Izraz "korisnički definiran" može biti malo pogrešan. C ++ pravi razliku između ugrađenih tipova i korisnički definiranih tipova. Prvi uključuje, na primjer, int, char i double; potonji uključuju sve tipove strukture, klase, unije i popisivanja, uključujući iz standardne knjižnice, čak i ako ih korisnici kao takvi ne definiraju.

2 Ovo je opisano u kasnijem dijelu ovog FAQ-a.

3 @ nije valjani C ++ operator, pa ga koristim kao rezervirano mjesto.

4 Jedan ternarni operator u C ++ ne može biti preopterećen, a jedan n-ary operator mora uvijek biti implementiran kao funkcija člana.


Nastavi Tri osnovna pravila za preopterećenje operatora u C ++ .

242
12 дек. odgovor je dan sbi 12 dec. 2010-12-12 15:46 '10 u 15:46 2010-12-12 15:46

Odluka između člana i nečlanica

Binarni operatori = (pridruživanje), [] (pretplata na nizove), -> (pristup članovima) i n-ary () operator (funkcija call) trebali bi uvijek biti implementirani kao članske funkcije , jer zahtijevaju sintaksu jezika ,

Ostali operatori mogu biti implementirani ili kao članovi ili kao nečlanovi. Neke od njih, međutim, obično moraju biti implementirane kao nečlanske funkcije, jer se njihov lijevi operand ne može promijeniti. Najistaknutiji od njih su ulazni i izlazni operatori << i >> , čiji su lijevi operandi klase tokova iz standardne knjižnice koje ne možete promijeniti.

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

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