Piezīme: Atbildes tika sniegtas noteiktā secībā, bet, tā kā daudzi lietotāji šķiro atbildes pēc balsojuma, nevis pēc to sniegšanas laika, šeit ir sniegts ___atbilžu indekss tādā secībā, kādā tām ir vislielākā nozīme:
(Piezīme: Šis ir domāts kā ieraksts Stack Overflow's C++ FAQ. Ja vēlaties kritizēt ideju par FAQ nodrošināšanu šādā formā, tad meta ieraksts, ar kuru tas viss sākās būtu īstā vieta, kur to darīt. Atbildes uz šo jautājumu tiek uzraudzītas C++ tērzētavā, kur FAQ ideja aizsākās vispirms, tāpēc ļoti iespējams, ka jūsu atbildi izlasīs tie, kas ierosināja šo ideju).
Lielākā daļa no operatora pārslodzes darba ir kopējais kods. Tas nav nekāds brīnums, jo operatori ir tikai sintaktiskais cukurs, un to faktisko darbu varētu veikt (un bieži vien arī tiek nodots) vienkāršas funkcijas. Taču ir svarīgi, lai šis "boilerplate" kods būtu pareizs. Ja jums tas neizdosies, vai nu jūsu operatora kods netiks kompilēts, vai arī jūsu lietotāju kods netiks kompilēts, vai arī lietotāju kods uzvedīsies pārsteidzoši.
Par piešķiršanu var daudz ko teikt. Tomēr lielākā daļa no tā jau ir teikta GMan's slavenajā Copy-And-Swap FAQ, tāpēc es šeit izlaidīšu lielāko daļu no tā, tikai atsauces nolūkos uzskaitot perfektu piešķiršanas operatoru:
X& X::operator=(X rhs)
{
swap(rhs);
return *this;
}
Lai gan bitshift operatori <<
un >>
joprojām tiek izmantoti aparatūras saskarnēs, lai gan tie ir bitu manipulācijas funkcijas, kas pārmantotas no C, lielākajā daļā lietojumprogrammu tie ir kļuvuši izplatītāki kā pārslogoti plūsmas ieejas un izejas operatori. Norādījumus par pārslodzi kā bitu manipulācijas operatoriem skatiet tālāk sadaļā par binārajiem aritmētiskajiem operatoriem. Lai īstenotu savu pielāgotu formāta un parsēšanas loģiku, kad jūsu objekts tiek izmantots ar iotecēm, turpiniet.
Straumju operatori, kas ir vieni no visbiežāk pārslogotajiem operatoriem, ir bināri infiksa operatori, kuriem sintakse nenosaka ierobežojumus attiecībā uz to, vai tiem jābūt locekļiem vai locekļiem.
Tā kā tie maina savu kreiso argumentu (maina plūsmas stāvokli), saskaņā ar vispārpieņemtajiem noteikumiem tie jārealizē kā sava kreisā operanda tipa locekļi. Tomēr to kreisie operandi ir standarta bibliotēkas plūsmas, un, lai gan lielākā daļa standarta bibliotēkā definēto plūsmas izvades un ievades operatoru patiešām ir definēti kā plūsmas klašu locekļi, īstenojot izvades un ievades operācijas saviem tipiem, jūs nevarat mainīt standarta bibliotēkas plūsmas tipus. Tāpēc šie operatori saviem tipiem jārealizē kā funkcijas, kas nav biedru funkcijas.
Abu šo operāciju kanoniskās formas ir šādas:
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( /* no valid object of T found in stream */ )
is.setstate(std::ios::failbit);
return is;
}
Īstenojot operatoru>>>
, manuāli iestatīt plūsmas stāvokli ir nepieciešams tikai tad, ja pati lasīšana ir izdevusies, bet rezultāts nav tāds, kāds būtu gaidīts.
Funkcijas izsaukuma operatoram, ko izmanto funkciju objektu, pazīstamu arī kā funktori, izveidei, jābūt definētam kā member funkcijai, tāpēc tam vienmēr ir netiešais this
arguments kā member funkcijām. Papildus tam to var pārslogot, lai tas varētu pieņemt jebkuru papildu argumentu skaitu, ieskaitot nulli.
Lūk, sintakses piemērs:
class foo {
public:
// Overloaded call operator
int operator()(const std::string& y) {
// ...
}
};
Lietošana:
foo f;
int a = f("hello");
Visā C++ standarta bibliotēkā funkciju objekti vienmēr tiek kopēti. Tāpēc jūsu pašu funkciju objektiem jābūt lētiem, lai tos varētu kopēt. Ja funkcijas objektam noteikti jāizmanto dati, kuru kopēšana ir dārga, labāk šos datus glabāt citur un funkcijas objektam uz tiem atsaukties.
Bināro infiksu salīdzināšanas operatori saskaņā ar īkšķa noteikumiem jārealizē kā funkcijas, kas nav biedru funkcijas1. Vienbalsīga prefiksa noliegums !
(saskaņā ar tiem pašiem noteikumiem) jārealizē kā locekļa funkcija. (bet parasti nav laba ideja to pārslogot.)
Standarta bibliotēkas algoritmi (piemēram, std::sort()
) un tipi (piemēram, std::map
) vienmēr sagaida tikai operator<
klātbūtni. Tomēr _tava tipa lietotāji sagaidīs, ka būs arī visi pārējie operatori, tāpēc, ja definē operator<
, pārliecinies, ka ievēro operatoru pārslodzes trešo pamatnoteikumu un definē arī visus pārējos bolu salīdzināšanas operatorus. Kanoniskais veids, kā tos ieviest, ir šāds:
inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
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);}
Svarīgi atzīmēt, ka tikai divi no šiem operatoriem faktiski kaut ko dara, pārējie tikai pārsūta savus argumentus vienam no šiem diviem operatoriem, lai tie veiktu faktisko darbu.
Sintakse pārējo bināro boolean operatoru (|||
, &&
) pārslogošanai atbilst salīdzināšanas operatoru noteikumiem. Tomēr ir ļoti maz ticams, ka jūs atradīsiet saprātīgu lietojumu šiem2.
1 Kā jau tas ir ar visiem noteikumiem, dažreiz var būt iemesli pārkāpt arī šo. Tādā gadījumā neaizmirstiet, ka bināro salīdzināšanas operatoru kreisajam operandam, kas locekļu funkcijām būs *this
, arī jābūt const
. Tātad salīdzināšanas operatoram, kas īstenots kā locekļa funkcija, būtu jābūt ar šādu parakstu:
bool operator<(const X& rhs) const { /* do actual comparison with *this */ }
(Ievērojiet const
beigās.)
2 Jāatzīmē, ka |||
un &&
iebūvētās versijas izmanto saīsņu semantiku. Savukārt lietotāja definētās (jo tās ir sintaktiskais cukurs metožu izsaukumiem) neizmanto saīsņu semantiku. Lietotājs sagaidīs, ka šiem operatoriem būs īsceļu semantika, un viņa kods var būt no tās atkarīgs, tāpēc ir ļoti ieteicams tos NEKAD nedefinēt.
Vienpakāpes inkrementēšanas un dekrementēšanas operatori ir gan prefiksa, gan postfiksa formā. Lai atšķirtu vienu no otra, postfiksa variantiem tiek izmantots papildu fiktīvs int arguments. Ja pārslogojat inkrementēšanu vai dekrementēšanu, vienmēr ievietojiet gan prefiksa, gan postfiksa versijas. Šeit ir kanoniskā inkrement implementācija, decrement darbojas pēc tādiem pašiem noteikumiem:
class X {
X& operator++()
{
// do actual increment
return *this;
}
X operator++(int)
{
X tmp(*this);
operator++();
return tmp;
}
};
Ņemiet vērā, ka postfiksa variants ir implementēts prefiksa izteiksmē. Ievērojiet arī to, ka postfikss veic papildu kopiju.2
Vienpakāpes mīnus un plus pārslodze nav pārāk izplatīta un, iespējams, no tās labāk izvairīties. Ja nepieciešams, tās, iespējams, jāpārlādē kā locekļu funkcijas.
2 Jāņem vērā arī tas, ka postfiksa variants veic vairāk darba un tāpēc ir mazāk efektīvs nekā prefiksa variants. Tas ir labs iemesls, kāpēc parasti priekšroka tiek dota prefiksa inkrementācijai, nevis postfiksa inkrementācijai. Lai gan kompilatori parasti var optimizēt postfiksa inkrementācijas papildu darbu iebūvētiem tipiem, tie var nespēt to pašu izdarīt lietotāja definētiem tipiem (kas var būt kaut kas tik nevainīgi izskatīgs kā saraksta iterators). Kad esat pieradis lietot i++
, kļūst ļoti grūti atcerēties, ka tā vietā ir jādara ++i
, ja i
nav iebūvēts tips (turklāt, mainot tipu, būtu jāmaina kods), tāpēc labāk ir ieradums vienmēr lietot prefiksa inkrementu, ja vien postfikss nav nepārprotami nepieciešams.
Attiecībā uz binārajiem aritmētiskajiem operatoriem neaizmirstiet ievērot trešo pamatnoteikumu par operatora pārslodzi: Ja sniedzat +
, sniedziet arī +=
, ja sniedzat -
, neizlaidiet -=
utt. Endrjū Kēnigs esot pirmais pamanījis, ka saliktos piešķiršanas operatorus var izmantot kā bāzi to nesaliktajiem analogiem. Tas nozīmē, ka operators +
ir realizēts ar +=
, -
ir realizēts ar -=
utt.
Saskaņā ar mūsu noteikumiem +
un tā pavadoņiem jābūt ne-locekļiem, bet to saliktajiem piešķiršanas analogiem (+=
u.c.), mainot to kreiso argumentu, jābūt locekļiem. Šeit ir dots +=
un +
parauga kods; pārējie binārie aritmētiskie operatori jārealizē tādā pašā veidā:
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;
}
operators+=`` atdod savu rezultātu uz vienu atsauci, bet
operators+`atdod sava rezultāta kopiju. Protams, atsauces atgriešana parasti ir efektīvāka nekā kopijas atgriešana, bet
operatora+gadījumā nav iespējams apiet kopēšanu. Rakstot
a + b, jūs sagaidāt, ka rezultāts būs jauna vērtība, tāpēc
operatoram+ir jāatgriež jauna vērtība.<sup>3</sup> Ievērojiet arī to, ka
operators+savu kreiso operandu ___kopējot___, nevis izmantojot konst atsauci. Iemesls tam ir tāds pats kā tam, ka
operators=ņem savu argumentu uz kopiju. Bitu manipulācijas operatori
~`&
|
^
<<
>>
jārealizē tāpat kā aritmētiskie operatori. Tomēr (izņemot <<
un >>
pārslodzi izvades un ievades operatoriem) to pārslodzei ir ļoti maz saprātīgu lietojuma gadījumu.
3 Atkal no tā var izdarīt secinājumu, ka a += b
kopumā ir efektīvāks nekā a + b
, un, ja iespējams, tam jādod priekšroka.
Masīva apakšraksta operators ir binārais operators, kas jāimplementē kā klases loceklis. To izmanto konteineriem līdzīgiem tipiem, kas ļauj piekļūt saviem datu elementiem ar atslēgas palīdzību. Kanoniskā to nodrošināšanas forma ir šāda:
class X {
value_type& operator[](index_type idx);
const value_type& operator[](index_type idx) const;
// ...
};
Ja vien jūs nevēlaties, lai jūsu klases lietotāji nevarētu mainīt datu elementus, ko atgriež operator[]
(tādā gadījumā varat izlaist nekonst variantu), jums vienmēr jānodrošina abi operatora varianti.
Ja ir zināms, ka value_type attiecas uz iebūvētu tipu, tad operatoram const variantā labāk atgriezt kopiju, nevis const atsauci:
class X {
value_type& operator[](index_type idx);
value_type operator[](index_type idx) const;
// ...
};
Lai definētu savus iteratorus vai viedos rādītājus, ir jāpārlādē vienpakāpes prefiksa dereferences operators *
un binārais infiksa rādītāja locekļa piekļuves operators ->
:
class my_ptr {
value_type& operator*();
const value_type& operator*() const;
value_type* operator->();
const value_type* operator->() const;
};
->
, ja vērtības_tips
ir klases
(vai konstrukcijas
, vai vienības
) tips, rekursīvi tiek izsaukts cits operators->()
, līdz operators->()
atgriež vērtību, kas nav klases tipa.
Vienpakāpes operatoru address-of nekad nedrīkst pārslogot.
Par operator->*()
skat. šis jautājums. Tas tiek reti izmantots un tāpēc reti kad tiek pārslogots. Patiesībā pat iteratori to nepārslogo.Turpināt uz Pārveidošanas operatori
Runājot par operatoru pārslodzi C++, ir trīs pamatnoteikumi, kas jāievēro. Tāpat kā visos šādos noteikumos, arī šeit ir izņēmumi. Dažreiz cilvēki ir novirzījušies no tiem, un rezultāts nav bijis slikts kods, taču šādu pozitīvu noviržu ir maz un tās ir ļoti retas. Vismaz 99 no 100 šādām atkāpēm, ko esmu redzējis, bija nepamatotas. Tomēr tikpat labi tās varētu būt bijušas 999 no 1000. Tāpēc labāk turieties pie šādiem noteikumiem.
Ja kāda operatora nozīme nav acīmredzami skaidra un neapstrīdama, to nevajadzētu pārslogot. Vai vietā piedāvājiet funkciju ar labi izvēlētu nosaukumu. Būtībā pirmais un galvenais operators pārslogošanas noteikums savā būtībā saka: Nedariet tā. Tas var šķist dīvaini, jo par operatoru pārslodzi ir daudz kas jāzina, tāpēc par to ir daudz rakstu, grāmatu nodaļu un citu tekstu. Taču, neraugoties uz šo šķietami acīmredzamo pierādījumu, ir tikai pārsteidzoši maz gadījumu, kad operatoru pārslodze ir piemērota. Iemesls ir tāds, ka patiesībā ir grūti saprast operatora lietojuma semantiku, ja vien operatora lietojums lietojuma jomā nav labi zināms un neapstrīdams. Pretēji vispārpieņemtajam uzskatam, tas gandrīz nekad nenotiek.
Visur pieturieties pie labi zināmās operatora semantikas..
C++ neuzliek nekādus ierobežojumus pārslogoto operatoru semantikai. Jūsu kompilators ar prieku pieņems kodu, kas implementē bināro +
operatoru, lai atņemtu no tā labā operanda. Tomēr šāda operatora lietotāji nekad neiedomāsies, ka izteiksme a + b
atņem a
no b
. Protams, tas nozīmē, ka operatora semantika lietojuma jomā ir neapstrīdama.
Visas no saistīto operāciju kopas vienmēr sniedz visas .
Operatori ir saistīti viens ar otru un ar citām operācijām. Ja jūsu tips atbalsta a + b
, lietotāji sagaida, ka varēs izsaukt arī a += b
. Ja tas atbalsta prefiksa inkrementu ++a
, viņi sagaidīs, ka a++
arī darbosies. Ja viņi var pārbaudīt, vai a < b
, viņi noteikti sagaida, ka varēs pārbaudīt arī, vai a > b
. Ja viņi var kopēt jūsu tipu, viņi sagaida, ka darbosies arī piešķiršana.
Turpināt uz Lēmums starp locekli un ne-locekli.
Operatoru nozīmi iebūvētajiem tipiem C++ nevar mainīt, operatorus var pārslogot tikai lietotāja definētajiem tipiem1. Tas nozīmē, ka vismaz vienam no operandiem jābūt lietotāja definētam tipam. Tāpat kā citas pārslogotās funkcijas, arī operatorus var pārslogot tikai vienreiz noteiktam parametru kopumam.
Ne visus operatorus var pārslogot programmā C++. Operatori, kurus nevar pārslogot, ir šādi: .
::
::
`sizeof
typeid
.*
un vienīgais trīskāršais operators C++, ?:
.
No operatoriem, kurus var pārslogot, C++ ir šādi:
+
-
*
/
%
un +=
-=
*=
/=
%=
%=
(visi bināri infiksi); +
-
(vienbalsīgs prefikss); ++
--
(vienbalsīgs prefikss un postfikss).&
|`^
<<``
>>un
&=`|=
^=
<<=
>>=
>>=
(visi binārie infiksi); ~
(vienbalsīgais prefikss)==
!=
<
<=
<=
>=
|||
&&
(visi bināri infiksi); !
(vienbalsīgs prefikss)new
new[]
`delete
delete[]
=
[]
->
->*
,
(visi bināri infiksi); *
&
(visi vienburtu prefiksi) ()
(funkcijas izsaukums, n-ārie infiksi)Tomēr tas, ka jūs varat visas šīs funkcijas pārslogot, nenozīmē, ka jums vajadzētu to darīt. Skatiet operatoru pārslodzes pamatnoteikumus.
C++ valodā operatori tiek pārslogoti kā funkcijas ar īpašiem nosaukumiem. Tāpat kā citas funkcijas, arī pārslogotos operatorus parasti var īstenot vai nu kā to kreisā operanda tipa funkciju vai kā nefunkciju funkcijas___. Tas, vai jūs varat brīvi izvēlēties vai esat spiesti izmantot vienu no tām, ir atkarīgs no vairākiem kritērijiem. 2 Vienmandātu operatoru @
3, ko piemēro objektam x, izsauc vai nu kā operator@(x)
, vai kā x.operator@()
. Bināro infiksa operatoru @
, ko piemēro objektiem x
un y
, izsauc vai nu kā operator@(x,y)
, vai kā x.operator@(y)
.4
Operatori, kas ir īstenoti kā funkcijas, kuras nav locekļu funkcijas, dažreiz ir draugi ar to operanda tipu.
1 Termins "lietotāja definēts" var būt nedaudz maldinošs. C++ izšķir iebūvētos tipus un lietotāja definētos tipus. Pie pirmajiem pieder, piemēram, int, char un double; pie otrajiem pieder visi struct, class, union un enum tipi, ieskaitot standarta bibliotēkas tipus, pat ja tie paši par sevi nav lietotāju definēti.
2 Tas ir aplūkots vēlākā daļā šajā FAQ.
3 @
nav derīgs operators C++, tāpēc es to izmantoju kā vietniekvārdu.
4 Vienīgais trīskāršais operators C++ valodā nav pārslogojams, un vienīgais n-ārais operators vienmēr jārealizē kā locekļa funkcija.
Turpināt uz Trīs pamatnoteikumi par operatora pārslodzi C++.