Operatör överbelastning

Den aktuella versionen av sidan har ännu inte granskats av erfarna bidragsgivare och kan skilja sig väsentligt från versionen som granskades den 9 juli 2018; kontroller kräver 25 redigeringar .

Operatörsöverbelastning i programmering  är ett av sätten att implementera polymorfism , som består i möjligheten av den samtidiga existensen i samma omfång av flera olika alternativ för att använda operatörer som har samma namn, men som skiljer sig i de typer av parametrar till vilka de är applicerad.

Terminologi

Termen " overload " är ett spårningspapper av det engelska ordet overloading . En sådan översättning dök upp i böcker om programmeringsspråk under första hälften av 1990-talet. I sovjetperiodens publikationer kallades liknande mekanismer omdefiniering eller omdefiniering , överlappande operationer.

Skäl till

Ibland finns det ett behov av att beskriva och tillämpa operationer på datatyper som skapats av programmeraren och som har samma betydelse som de som redan finns tillgängliga på språket. Ett klassiskt exempel är biblioteket för att arbeta med komplexa tal . De, liksom vanliga numeriska typer, stöder aritmetiska operationer, och det skulle vara naturligt att skapa för denna typ av operation "plus", "minus", "multiplicera", "divide", och beteckna dem med samma operationstecken som för andra numeriska typer. Förbudet mot användning av element definierade i språket tvingar fram skapandet av många funktioner med namn som ComplexPlusComplex, IntegerPlusComplex, ComplexMinusFloat, och så vidare.

När operationer av samma betydelse tillämpas på operander av olika typer, tvingas de namnges olika. Oförmågan att använda funktioner med samma namn för olika typer av funktioner leder till att man måste hitta på olika namn för samma sak, vilket skapar förvirring och kan till och med leda till fel. Till exempel, i det klassiska C-språket, finns det två versioner av standardbiblioteksfunktionen för att hitta modulen för ett tal: abs() och fabs() - den första är för ett heltalsargument, den andra för ett riktigt. Denna situation, i kombination med svag C-typkontroll, kan leda till ett svårtillgängligt fel: om en programmerare skriver abs(x) i beräkningen, där x är en verklig variabel, kommer vissa kompilatorer att generera kod utan förvarning som omvandla x till ett heltal genom att kassera bråkdelarna och beräkna modulen från det resulterande heltal.

Delvis löses problemet med hjälp av objektprogrammering - när nya datatyper deklareras som klasser kan operationer på dem formaliseras som klassmetoder, inklusive klassmetoder med samma namn (eftersom metoder för olika klasser inte behöver ha olika namn), men för det första är ett sådant designsätt för värderingar av olika typer obekvämt, och för det andra löser det inte problemet med att skapa nya operatörer.

Verktyg som låter dig utöka språket, komplettera det med nya operationer och syntaktiska konstruktioner (och överbelastning av operationer är ett av sådana verktyg, tillsammans med objekt, makron, funktionaliteter, stängningar) gör det till ett metaspråk  - ett verktyg för att beskriva språk fokuserade på specifika uppgifter. Med dess hjälp är det möjligt att bygga en språktillägg för varje specifik uppgift som är mest lämplig för den, vilket gör det möjligt att beskriva lösningen i den mest naturliga, begripliga och enkla formen. Till exempel, i en applikation för överbelastningsoperationer: att skapa ett bibliotek av komplexa matematiska typer (vektorer, matriser) och beskriva operationer med dem i en naturlig, "matematisk" form, skapar ett "språk för vektoroperationer", där komplexiteten av beräkningar är dolda, och det är möjligt att beskriva lösningen av problem i termer av vektor- och matrisoperationer, med fokus på problemets essens, inte på tekniken. Det var av dessa skäl som sådana medel en gång ingick i Algol-68- språket .

Överbelastningsmekanism

Implementering

Operatörsöverbelastning innebär införandet av två inbördes relaterade funktioner i språket: förmågan att deklarera flera procedurer eller funktioner med samma namn i samma omfång, och förmågan att beskriva dina egna implementeringar av binära operatorer (det vill säga tecken på operationer, vanligtvis skrivet med infix notation, mellan operander). I grund och botten är deras implementering ganska enkel:

Operatörsöverbelastning i C++

Det finns fyra typer av operatörsöverbelastning i C++:

  1. Överbelastning av vanliga operatörer + - * / % ˆ & | ~! = < > += -= *= /= %= ˆ= &= |= << >> >>= <<= == != <= >= && || ++ -- , ->* -> ( ) <=> [ ]
  2. Överbelastning av typkonverteringsoperatörer
  3. Överbelastning av '''ny'''-allokering och '''radera''' -operatorer för objekt i minnet.
  4. Överbelastade operatör"" bokstaver
Vanliga operatorer

Det är viktigt att komma ihåg att överbelastning förbättrar språket, det ändrar inte språket, så du kan inte överbelasta operatörer för inbyggda typer. Du kan inte ändra prioritet och associativitet (vänster till höger eller höger till vänster) för operatorer. Du kan inte skapa dina egna operatörer och överbelasta några av de inbyggda: :: . .* ?: sizeof typeid. Operatörer && || ,förlorar också sina unika egenskaper när de överbelastas: lathet för de två första och företräde för ett kommatecken (ordningen för uttryck mellan kommatecken är strikt definierad som vänsterassociativ, det vill säga vänster till höger). Operatören ->måste returnera antingen en pekare eller ett objekt (genom kopia eller referens).

Operatörer kan överbelastas både som fristående funktioner och som medlemsfunktioner i en klass. I det andra fallet är det vänstra argumentet för operatorn alltid *detta objekt. Operatörer = -> [] ()kan bara överbelastas som metoder (medlemsfunktioner), inte som funktioner.

Du kan göra det mycket lättare att skriva kod om du överbelasta operatörer i en viss ordning. Detta kommer inte bara att påskynda skrivningen, utan också rädda dig från att duplicera samma kod. Låt oss överväga en överbelastning med exemplet på en klass som är en geometrisk punkt i ett tvådimensionellt vektorrum:

classPoint _ { int x , y ; offentliga : Punkt ( int x , int xx ) : x ( x ), y ( xx ) {} // Standardkonstruktorn är borta. // Konstruktörargumentnamn kan vara samma som klassfältnamn. }
  • Kopiera och flytta tilldelningsoperatorer operator=
    Det är värt att tänka på att C++ som standard skapar fem grundläggande funktioner utöver konstruktorn. Därför är det bäst att kopiera och flytta överbelastning av tilldelningsoperatorer till kompilatorn eller implementeras med hjälp av kopiera-och-byta idiom .
  • Kombinerade aritmetiska operatorer += *= -= /= %=etc.
    Om vi ​​vill implementera vanliga binära aritmetiska operatorer är det bekvämare att implementera denna grupp av operatorer först.Point & Point :: operator += ( const Point & rhs ) { x += rhs . x ; y += rhs . y ; returnera * detta ; }
Operatören returnerar ett värde genom referens, detta låter dig skriva sådana konstruktioner:(a += b) += c;
  • Aritmetiska operatorer + * - / %
    För att bli av med kodupprepning, låt oss använda vår kombinerade operator. Operatören ändrar inte objektet, så den returnerar ett nytt objekt.const Point Point :: operator + ( const Point & rhs ) const { returpunkt ( * detta ) + = rhs ; }
Operatören returnerar ett const-värde. Detta kommer att skydda oss från att skriva konstruktioner av det här slaget (a + b) = c;. Å andra sidan, för klasser som är dyra att kopiera är det mycket mer lönsamt att returnera ett värde från en icke-konstant kopia, det vill säga : MyClass MyClass::operator+(const MyClass& rhs) const;. Sedan, med en sådan post x = y + z;, kommer flyttkonstruktorn att anropas, inte kopieringskonstruktorn.
  • Unära aritmetiska operatorer + -
    De unära plus- och minusoperatorerna tar inga argument när de är överbelastade. De ändrar inte själva objektet (i vårt fall), utan returnerar ett nytt modifierat objekt. Du bör också överbelasta dem om deras binära motsvarigheter är överbelastade.
Point Point :: operator + () { returnPoint ( * detta ) ; } Point Point :: operator - () { punkt tmp ( * detta ); tmp . x *= -1 ; tmp . y *= -1 ; returnera tmp ; }
  • Jämförelseoperatörer == != < <= > >=
    Det första man ska göra är att överbelasta jämställdhets- och ojämlikhetsoperatörerna. Ojämlikhetsoperatören kommer att använda jämställdhetsoperatören.
bool Point :: operator == ( const Point & rhs ) const { return ( detta -> x == rhs . x && this -> y == rhs . y ); } bool Point :: operator != ( const Point & rhs ) const { återvända ! ( * detta == rhs ); } Därefter överbelastas < och > operatörerna, och sedan deras icke-stränga motsvarigheter, med de tidigare överbelastade operatörerna. För punkter i geometri är en sådan operation inte definierad, så i det här exemplet är det ingen idé att överbelasta dem.
  • Bitvisa operatorer <<= >>= &= |= ^= и << >> & | ^ ~
    De är föremål för samma principer som aritmetiska operatorer. I vissa klasser kommer användningen av en bitmask att vara praktisk std::bitset. Obs: Operatören & har en unär motsvarighet och används för att ta en adress; vanligtvis inte överbelastad.
  • Logiska operatörer && ||
    Dessa operatörer kommer att förlora sina unika lättjaegenskaper när de överbelastas.
  • Öka och minska ++ --
    C++ låter dig överbelasta både postfix- och prefixökning och minskning. Tänk på en ökning:
Point & Point :: operator ++ () { // prefix x ++ ; y ++ ; returnera * detta ; } Point Point :: operator ++ ( int ) { //postfix Point tmp ( x , y , i ); ++ ( * detta ); returnera tmp ; } Observera att medlemsfunktionen operator++(int) tar ett värde av typen int, men detta argument har inget namn. C++ låter dig skapa sådana funktioner. Vi kan ge det (argumentet) ett namn och öka värdena på punkterna med denna faktor, men i operatorform kommer detta argument som standard till noll och kan endast anropas i funktionell stil:A.operator++(5);
  • Operatorn () har inga begränsningar för returtyp och typer/antal argument, och låter dig skapa funktorer .
  • En operatör för att skicka en klass till utgångsströmmen. Implementerad som en separat funktion, inte en medlemsfunktion. I klassen är denna funktion markerad som vänlig.friend std::ostream& operator<<(const ostream& s, const Point& p);

Andra operatörer omfattas inte av några allmänna riktlinjer för överbelastning.

Typkonverteringar

Typkonverteringar låter dig specificera reglerna för att konvertera vår klass till andra typer och klasser. Du kan också ange den explicita specifikationen, som endast tillåter typkonvertering om programmeraren uttryckligen har angett det (till exempel static_cast<Point3>(Point(2,3)); ). Exempel:

Point :: operator bool () const { returnera detta -> x != 0 || detta -> y != 0 ; } Tilldelnings- och avallokeringsoperatörer

Operatörer new new[] delete delete[]kan vara överbelastade och kan ta valfritt antal argument. Dessutom måste operatorer new и new[]ta ett typargument som det första argumentet std::size_toch returnera ett värde av typ void *, och operatorer måste ta det delete delete[]första void *och inte returnera något ( void). Dessa operatörer kan överbelastas både för funktioner och för betongklasser.

Exempel:

void * MyClass :: operator new ( std :: size_t s , int a ) { void * p = malloc ( s * a ); if ( p == nullptr ) kasta "Inget ledigt minne!" ; returnera p ; } // ... // Call: MyClass * p = new ( 12 ) MyClass ;


Anpassade bokstaver

Anpassade bokstaver har funnits sedan den elfte C++-standarden. Bokstavar beter sig som vanliga funktioner. De kan vara inline- eller constexpr-kvalificeringar . Det är önskvärt att det bokstavliga börjar med ett understreck, eftersom det kan finnas en konflikt med framtida standarder. Till exempel hör det bokstavliga i redan till de komplexa talen från std::complex.

Bokstaver kan bara ta en av följande typer: const char * , unsigned long long int , long double , char , wchar_t , char16_t , char32_t. Det räcker med att överbelasta bokstaven endast för typen const char * . Om ingen mer lämplig kandidat hittas kommer en operatör med den typen att anropas. Ett exempel på att konvertera miles till kilometer:

constexpr int operator "" _mi ( osignerad lång lång int i ) { return 1,6 * i ;} constexpr dubbeloperator " " _mi ( lång dubbel i ) { return 1,6 * i ;}

Strängliteraler tar ett andra argument std::size_toch ett av de första: const char * , const wchar_t *, const char16_t * , const char32_t *. Strängbokstavar gäller för poster inom dubbla citattecken.

C++ har en inbyggd prefixsträng bokstavlig R som behandlar alla citattecken som vanliga tecken och inte tolkar vissa sekvenser som specialtecken. Till exempel kommer ett sådant kommando std::cout << R"(Hello!\n)"att visa Hello!\n.

Implementeringsexempel i C#

Operatörsöverbelastning är nära relaterad till metodöverbelastning. En operatör är överbelastad med nyckelordet Operatör, som definierar en "operatörsmetod", som i sin tur definierar operatörens agerande med avseende på dess klass. Det finns två former av operatormetoder (operator): en för unära operatorer , den andra för binära . Nedan är den allmänna formen för varje variant av dessa metoder.

// allmän form av unär operatörsöverbelastning. public static return_type operator op ( parameter_type operand ) { // operations } // Allmän form av binär operatoröverbelastning. public static return_type operator op ( parameter_type1 operand1 , parameter_type2 operand2 ) { // operations }

Här, istället för "op", ersätts en överbelastad operator, till exempel + eller /; och "returtyp" betecknar den specifika typen av värde som returneras av den specificerade operationen. Detta värde kan vara av vilken typ som helst, men det anges ofta vara av samma typ som den klass för vilken operatören överbelastas. Denna korrelation gör det lättare att använda överbelastade operatorer i uttryck. För unära operatorer betecknar operanden den operand som skickas, och för binära operatorer betecknas densamma med "operand1 och operand2". Observera att operatörsmetoder måste vara av båda typerna, offentliga och statiska. Operandtypen för unära operatörer måste vara samma som den klass för vilken operatören överbelastas. Och i binära operatorer måste minst en av operanderna vara av samma typ som dess klass. Därför tillåter inte C# att några operatorer överbelastas på objekt som ännu inte har skapats. Tilldelningen av operatorn + kan till exempel inte åsidosättas för element av typen int eller string . Du kan inte använda ref eller out-modifieraren i operatörsparametrar. [ett]

Alternativ och problem

Att överbelasta procedurer och funktioner på en generell idénivå är som regel inte svårt att implementera eller förstå. Men även i den finns det några "fallgropar" som måste beaktas. Att tillåta operatörsöverbelastning skapar mycket mer problem för både språkimplementatorn och programmeraren som arbetar på det språket.

Identifieringsproblem

Det första problemet är sammanhangsberoende . Det vill säga, den första frågan som en utvecklare av en språköversättare som tillåter överbelastning av procedurer och funktioner står inför är: hur man väljer bland procedurerna med samma namn den som ska tillämpas i det här specifika fallet? Allt är bra om det finns en variant av proceduren, vars typer av formella parametrar exakt matchar typerna av de faktiska parametrarna som används i detta samtal. Men på nästan alla språk finns det en viss grad av frihet i användningen av typer, förutsatt att kompilatorn i vissa situationer automatiskt säkert konverterar (castar) datatyper. Till exempel, i aritmetiska operationer på reella och heltalsargument, konverteras ett heltal vanligtvis automatiskt till en reell typ, och resultatet är reellt. Anta att det finns två varianter av add-funktionen:

int add(int a1, int a2); float add(float a1, float a2);

Hur ska kompilatorn hantera uttrycket y = add(x, i)där x är av typen float och i är av typen int? Det finns uppenbarligen ingen exakt matchning. Det finns två alternativ: antingen y=add_int((int)x,i), eller som (här betecknas den första och andra versionen av funktionen med respektive y=add_flt(x, (float)i)namn ).add_intadd_flt

Frågan uppstår: ska kompilatorn tillåta denna användning av överbelastade funktioner, och i så fall, på vilken grund kommer den att välja den specifika variant som används? Särskilt, i exemplet ovan, bör översättaren överväga typen av variabel y när han väljer? Det bör noteras att den givna situationen är den enklaste. Men mycket mer komplicerade fall är möjliga, som förvärras av det faktum att inte bara inbyggda typer kan konverteras enligt språkets regler, utan även klasser som deklareras av programmeraren, om de har släktskapsförhållanden, kan gjutas från en till en annan. Det finns två lösningar på detta problem:

  • Förbjud överhuvudtaget felaktig identifiering. Kräv att det för varje särskilt par av typer finns en exakt lämplig variant av den överbelastade proceduren eller operationen. Om det inte finns något sådant alternativ bör kompilatorn ge ett fel. Programmeraren i detta fall måste tillämpa en explicit konvertering för att casta de faktiska parametrarna till den önskade uppsättningen av typer. Detta tillvägagångssätt är obekvämt i språk som C++, som tillåter en hel del frihet att hantera typer, eftersom det leder till en betydande skillnad i beteendet hos inbyggda och överbelastade operatorer (arithmetiska operationer kan tillämpas på vanliga tal utan att tänka, utan till andra typer - bara med explicit konvertering) eller till uppkomsten av ett stort antal alternativ för operationer.
  • Upprätta vissa regler för att välja "närmaste passform". Vanligtvis, i denna variant, väljer kompilatorn de av varianterna vars anrop kan erhållas från källan endast genom säkra (icke-förlustinformation) typkonverteringar, och om det finns flera av dem kan den välja baserat på vilken variant som kräver färre sådana omvandlingar. Om resultatet lämnar mer än en möjlighet, ger kompilatorn ett fel och kräver att programmeraren explicit specificerar varianten.
Operation Överbelastning specifika problem

Till skillnad från procedurer och funktioner har infix-operationer för programmeringsspråk två ytterligare egenskaper som avsevärt påverkar deras funktionalitet: prioritet och associativitet , vars närvaro beror på möjligheten till "kedje"-inspelning av operatörer (hur man förstår a+b*c : hur (a+b)*celler hur a+(b*c)? Uttryck a-b+c - detta (a-b)+celler a-(b+c)?).

Operationerna som är inbyggda i språket har alltid fördefinierad traditionell företräde och associativitet. Frågan uppstår: vilka prioriteringar och associativitet kommer de omdefinierade versionerna av dessa operationer att ha, eller dessutom de nya operationerna som skapats av programmeraren? Det finns andra finesser som kan kräva förtydligande. Till exempel, i C finns det två former av inkrement- och dekrementoperatorerna ++och -- , prefix och postfix, som beter sig olika. Hur ska de överbelastade versionerna av sådana operatörer bete sig?

Olika språk hanterar dessa frågor på olika sätt. Så i C++ bevaras prioriteten och associativiteten för överbelastade versioner av operatorer på samma sätt som de för fördefinierade versioner i språket, och överbelastade beskrivningar av prefix- och postfixformerna för inkrement- och dekrementoperatorerna använder olika signaturer:

prefixform Postfix-formulär
Fungera T&operatör ++(T&) T-operator ++(T &, int)
medlemsfunktion T&T::operator ++() TT::operator ++(int)

Faktum är att operationen inte har en heltalsparameter - den är fiktiv och läggs bara till för att göra skillnad i signaturerna

En fråga till: är det möjligt att tillåta operatörsöverbelastning för inbyggda och för redan deklarerade datatyper? Kan en programmerare ändra implementeringen av tilläggsoperationen för den inbyggda integraltypen? Eller för bibliotekstypen "matris"? Den första frågan besvaras i regel nekande. Att ändra beteendet för standardoperationer för inbyggda typer är en extremt specifik åtgärd, vars verkliga behov kan uppstå endast i sällsynta fall, medan de skadliga konsekvenserna av den okontrollerade användningen av en sådan funktion är svåra att ens helt förutse. Därför förbjuder språket vanligtvis antingen omdefiniering av operationer för inbyggda typer, eller implementerar en operatörsöverbelastningsmekanism på ett sådant sätt att standardoperationer helt enkelt inte kan åsidosättas med dess hjälp. När det gäller den andra frågan (omdefiniering av operatorer som redan beskrivits för befintliga typer), tillhandahålls den nödvändiga funktionaliteten helt av mekanismen för klassarv och metodöverstyrning: om du vill ändra beteendet för en befintlig klass måste du ärva den och omdefiniera de operatörer som beskrivs i den. I det här fallet kommer den gamla klassen att förbli oförändrad, den nya kommer att få nödvändig funktionalitet och inga kollisioner kommer att inträffa.

Tillkännagivande av ny verksamhet

Situationen med tillkännagivandet av nya operationer är ännu mer komplicerad. Att inkludera möjligheten till en sådan förklaring på språket är inte svårt, men dess genomförande är fyllt med betydande svårigheter. Att deklarera en ny operation är i själva verket att skapa ett nytt nyckelord för programmeringsspråk, komplicerat av det faktum att operationer i texten som regel kan följa utan separatorer med andra tokens. När de dyker upp uppstår ytterligare svårigheter i organisationen av den lexikala analysatorn. Till exempel, om språket redan har operationerna "+" och det unära "-" (teckenändring), kan uttrycket a+-bkorrekt tolkas som a + (-b), men om en ny operation deklareras i programmet +-uppstår omedelbart tvetydighet, eftersom samma uttryck kan redan analyseras och hur a (+-) b. Utvecklaren och implementeraren av språket måste hantera sådana problem på något sätt. Alternativen, återigen, kan vara olika: kräv att alla nya operationer är enstaka tecken, postulera att vid eventuella avvikelser väljs den "längsta" versionen av operationen (det vill säga tills nästa uppsättning tecken läses av översättaren matchar vilken operation som helst, den fortsätter att läsas), försök att upptäcka kollisioner under översättning och generera fel i kontroversiella fall ... På ett eller annat sätt löser språk som tillåter deklarationen av nya operationer dessa problem.

Det bör inte glömmas bort att för nya verksamheter finns det också frågan om att bestämma associativitet och prioritet. Det finns inte längre en färdig lösning i form av en standardspråkoperation, och vanligtvis är det bara att ställa in dessa parametrar med språkets regler. Gör till exempel alla nya operationer vänsterassociativa och ge dem samma, fasta, prioritet, eller introducera i språket sättet att specificera båda.

Överbelastning och polymorfa variabler

När överbelastade operatorer, funktioner och procedurer används i starkt skrivna språk, där varje variabel har en fördeklarerad typ, är det upp till kompilatorn att bestämma vilken version av den överbelastade operatorn som ska användas i varje särskilt fall, oavsett hur komplext . Detta innebär att för kompilerade språk minskar inte användningen av operatörsöverbelastning prestanda på något sätt - i alla fall finns det ett väldefinierat operations- eller funktionsanrop i programmets objektkod. Situationen är annorlunda när det är möjligt att använda polymorfa variabler i språket - variabler som kan innehålla värden av olika slag vid olika tidpunkter.

Eftersom typen av värde som den överbelastade operationen kommer att tillämpas på är okänd vid tidpunkten för kodöversättning, berövas kompilatorn möjligheten att välja det önskade alternativet i förväg. I den här situationen tvingas det bädda in ett fragment i objektkoden som, omedelbart innan den här operationen utförs, kommer att bestämma typerna av värdena i argumenten och dynamiskt välja en variant som motsvarar denna uppsättning typer. Dessutom måste en sådan definition göras varje gång operationen utförs, eftersom till och med samma kod, som kallas en andra gång, mycket väl kan exekveras annorlunda ...

Användningen av operatöröverbelastning i kombination med polymorfa variabler gör det således oundvikligt att dynamiskt bestämma vilken kod som ska anropas.

Kritik

Användningen av överbelastning anses inte vara en välsignelse av alla experter. Om funktions- och proceduröverbelastning i allmänhet inte finner några allvarliga invändningar (delvis för att det inte leder till några typiska "operatörs"-problem, dels för att det är mindre frestande att missbruka det), då operatöröverbelastning, som i princip, och i specifik språkimplementationer, utsätts för ganska hård kritik från många programmeringsteoretiker och praktiker.

Kritiker påpekar att problemen med identifiering, företräde och associativitet som beskrivs ovan ofta gör det onödigt svårt eller onaturligt att hantera överbelastade operatörer:

  • Identifiering. Om språket har strikta identifieringsregler, tvingas programmeraren att komma ihåg för vilka kombinationer av typer det finns överbelastade operationer och manuellt kasta operander till dem. Om språket tillåter "ungefärlig" identifiering kan man aldrig vara säker på att i någon ganska komplicerad situation kommer exakt den variant av operationen som programmeraren hade i åtanke att utföras.
    • "Överbelastning" av en operation för en viss typ avgörs enkelt om språket stöder arv eller gränssnitt ( typklasser ). Om språket inte tillåter detta är det ett designproblem. Så i OOP-språk ( Java , C# ) ärvs metodoperatorer från Object, och inte från motsvarande klasser (jämförelse, numeriska operationer, bitvis, etc.) eller fördefinierade gränssnitt.
    • "Ungefärlig identifiering" finns bara på språk med ett system med lös typ, där " förmågan att skjuta dig själv i foten " "i en ganska svår situation" är permanent närvarande och utan att operatören överbelastas.
  • Prioritet och associativitet. Om de är strikt definierade kan detta vara obekvämt och inte relevant för ämnesområdet (till exempel för operationer med uppsättningar skiljer sig prioriteringar från aritmetiska). Om de kan ställas in av programmeraren blir detta en extra felgenerator (om så bara för att olika varianter av en operation visar sig ha olika prioriteringar, eller till och med associativitet).
    • Detta problem löses delvis genom att definiera nya operatorer (till exempel \/både /\för disjunktion och konjunktion ).

Hur mycket bekvämligheten med att använda sin egen verksamhet kan uppväga besväret med försämrad programhanterbarhet är en fråga som inte har ett tydligt svar.

Vissa kritiker talar emot överbelastningsoperationer, baserade på de allmänna principerna för mjukvaruutvecklingsteori och verklig industriell praxis.

  • Anhängare av det "puritanska" förhållningssättet till konstruktion av språk, som Wirth eller Hoare , motsätter sig överbelastning av operatörer bara för att det påstås vara lätt att klara sig utan. Enligt deras åsikt komplicerar sådana verktyg bara språket och översättaren, utan att tillhandahålla ytterligare funktioner som motsvarar denna komplikation. Enligt deras åsikt ser själva idén att skapa en uppgiftsorienterad förlängning av språket bara attraktiv ut. I verkligheten gör användningen av språktilläggsverktyg programmet begripligt endast för dess författare - den som utvecklade denna tillägg. Programmet blir mycket svårare för andra programmerare att förstå och analysera, vilket gör underhåll, modifiering och teamutveckling svårare.
  • Det noteras att själva möjligheten att använda överbelastning ofta spelar en provocerande roll: programmerare börjar använda det där det är möjligt, som ett resultat blir ett verktyg utformat för att förenkla och effektivisera programmet orsaken till dess överdrivna komplikationer och förvirring.
  • Överbelastade operatörer kanske inte gör exakt vad som förväntas av dem, baserat på deras typ. Till exempel a + bbetyder det vanligtvis (men inte alltid) detsamma som b + amen «один» + «два»skiljer sig från «два» + «один»på språk där operatören +är överbelastad för strängsammansättning .
  • Operatörsöverbelastning gör programfragment mer kontextkänsliga. Utan att veta vilka typer av operander som ingår i ett uttryck är det omöjligt att förstå vad uttrycket gör om det använder överbelastade operatorer. Till exempel, i ett C++- program kan en operator <<betyda både en bitvis förskjutning, utdata till en ström och en förskjutning av tecken i en sträng med ett givet antal positioner. Uttrycket a << 1returnerar:
    • resultatet av att bitvis skifta värdet aen bit till vänster if aär ett heltal;
    • om a - en sträng, så blir resultatet en sträng med ett mellanslagstecken lagt till i slutet (en tecken-för-teckenförskjutning kommer att göras med 1 position till vänster), och i olika datorsystem koden för mellanslagstecknet kan skilja sig åt;
    • men om aär en utström , kommer samma uttryck att mata ut siffran 1 till den strömmen «1».

Detta problem följer naturligt av de två föregående. Det utjämnas lätt av acceptansen av avtal och den allmänna programmeringskulturen.

Klassificering

Följande är en klassificering av vissa programmeringsspråk beroende på om de tillåter operatörsöverbelastning och om operatörer är begränsade till en fördefinierad uppsättning:

Många
operatörer

Ingen överbelastning

Det finns en överbelastning
Endast
fördefinierade

C
Java
JavaScript
Objective-C
Pascal
PHP
ActionScript
Go

Ada
C++
C#
D
Object Pascal
Perl
Python
Ruby
VB.NET
Delphi
Kotlin
Rust
Swift

Häftig

Det är möjligt
att introducera nya

ML
Pico
Lisp

Algol 68
Fortran
Haskell
PostgreSQL
Prologue
Perl 6
Seed7
Smalltalk
Julia

Anteckningar

  1. Herbert Schildt. Den kompletta guiden till C# 4.0, 2011.

Se även