Kuljettajan ylikuormitus

Kokeneet kirjoittajat eivät ole vielä tarkistaneet sivun nykyistä versiota, ja se voi poiketa merkittävästi 9. heinäkuuta 2018 tarkistetusta versiosta . tarkastukset vaativat 25 muokkausta .

Operaattoreiden ylikuormitus ohjelmoinnissa on  yksi tavoista toteuttaa polymorfismi , joka koostuu mahdollisuudesta olla samanaikaisesti samassa laajuudessa useita eri vaihtoehtoja käyttää operaattoreita, joilla on sama nimi, mutta jotka eroavat parametrien tyypeistä, joihin ne on tarkoitettu. sovelletaan.

Terminologia

Termi " overload " on kuultopaperi englanninkielisestä sanasta overloading . Tällainen käännös ilmestyi ohjelmointikieliä koskevissa kirjoissa 1990-luvun ensimmäisellä puoliskolla. Neuvostoajan julkaisuissa vastaavia mekanismeja kutsuttiin uudelleenmäärittelyksi tai uudelleenmäärittelyksi , päällekkäisiksi toimiksi.

Syitä

Joskus on tarve kuvata ja soveltaa ohjelmoijan luomiin tietotyyppeihin operaatioita, jotka vastaavat merkitykseltään kielellä jo saatavilla olevia. Klassinen esimerkki on kirjasto kompleksilukujen käsittelyä varten . Ne, kuten tavalliset numeeriset tyypit, tukevat aritmeettisia operaatioita, ja tälle operaatiolle olisi luonnollista luoda "plus", "miinus", "kerto", "jakaa" merkitsemällä ne samoilla operaatiomerkeillä kuin muille numeerisille operaatioille. tyypit. Kielellä määriteltyjen elementtien käyttökielto pakottaa luomaan monia funktioita nimillä, kuten ComplexPlusComplex, IntegerPlusComplex, ComplexMinusFloat ja niin edelleen.

Kun eri tyyppisiin operandeihin sovelletaan samaa merkitystä omaavia operaatioita, ne pakotetaan nimeämään eri tavalla. Kyvyttömyys käyttää samannimisiä funktioita erityyppisille funktioille johtaa siihen, että samalle asialle on keksittävä eri nimiä, mikä aiheuttaa sekaannusta ja voi jopa johtaa virheisiin. Esimerkiksi klassisessa C-kielessä on kaksi versiota vakiokirjastofunktiosta luvun moduulin löytämiseksi: abs() ja fabs() - ensimmäinen on kokonaislukuargumenttia varten, toinen todellista argumenttia varten. Tämä tilanne yhdistettynä heikon C-tyypin tarkistukseen voi johtaa vaikeasti löydettäviin virheisiin: jos ohjelmoija kirjoittaa abs(x):n laskelmaan, missä x on todellinen muuttuja, jotkin kääntäjät generoivat koodia ilman varoitusta, joka muuntaa x kokonaisluvuksi hylkäämällä murto-osat ja laskea moduuli tuloksena olevasta kokonaisluvusta.

Osittain ongelma ratkeaa olioohjelmoinnin avulla - kun uudet tietotyypit määritellään luokiksi, niille tehtävät toiminnot voidaan formalisoida luokkametodeina, mukaan lukien samannimiset luokkamenetelmät (koska eri luokkien menetelmillä ei tarvitse olla eri nimet), mutta ensinnäkin tällainen suunnittelutapa erityyppisille arvoille on hankalaa, ja toiseksi se ei ratkaise uusien operaattoreiden luomisen ongelmaa.

Työkalut, joilla voit laajentaa kieltä, täydentää sitä uusilla operaatioilla ja syntaktisilla rakenteilla (ja operaatioiden ylikuormitus on yksi tällaisista työkaluista objektien, makrojen, funktionaalisten toimintojen, sulkemisten ohella) muuttavat kielen metakieleksi  - työkaluksi kielten kuvaamiseen keskittynyt tiettyihin tehtäviin. Sen avulla on mahdollista rakentaa kullekin tietylle tehtävälle sille sopivin kielilaajennus, jonka avulla sen ratkaisua voidaan kuvata luonnollisimmassa, ymmärrettävämmässä ja yksinkertaisimmassa muodossa. Esimerkiksi sovelluksessa operaatioiden ylikuormitusta varten: monimutkaisten matemaattisten tyyppien (vektorit, matriisit) kirjaston luominen ja operaatioiden kuvaaminen niillä luonnollisessa, "matemaattisessa" muodossa, luo "vektorioperaatioiden kielen", jossa monimutkaisuus laskelmat on piilotettu, ja ongelmien ratkaisua on mahdollista kuvata vektori- ja matriisioperaatioilla keskittyen ongelman olemukseen, ei tekniikkaan. Näistä syistä sellaiset keinot sisällytettiin kerran Algol-68 -kieleen .

Ylikuormitusmekanismi

Toteutus

Operaattoreiden ylikuormitukseen sisältyy kahden toisiinsa liittyvän ominaisuuden tuominen kieleen: kyky ilmoittaa useita samalla nimellä olevia proseduureja tai funktioita samassa laajuudessa ja kyky kuvata omia binäärioperaattoreiden toteutuksia (eli operaatioiden merkkejä, yleensä kirjoitettu infix-merkinnällä, operandien väliin). Periaatteessa niiden toteutus on melko yksinkertaista:

Operaattorin ylikuormitus C++:ssa

C++:ssa on neljä erilaista operaattorin ylikuormitusta:

  1. Tavallisten operaattoreiden ylikuormitus + - * / % ˆ & | ~ ! = < > += -= *= /= %= ˆ= &= |= << >> >>= <<= == != <= >= && || ++ -- , ->* -> ( ) <=> [ ]
  2. Ylikuormitustyyppien muunnosoperaattorit
  3. Muistissa olevien objektien ''''new'''-allokointi- ja ''delete'''- operaattorien ylikuormitus .
  4. Ylikuormitetaan operaattoria""- kirjaimella
Tavalliset operaattorit

On tärkeää muistaa, että ylikuormitus parantaa kieltä, se ei muuta kieltä, joten sisäänrakennettujen tyyppien operaattoreita ei voi ylikuormittaa. Et voi muuttaa operaattoreiden ensisijaisuutta ja assosiatiivisuutta (vasemmalta oikealle tai oikealta vasemmalle). Et voi luoda omia operaattoreita ja ylikuormittaa joitain sisäänrakennetuista: :: . .* ?: sizeof typeid. Lisäksi operaattorit && || ,menettävät ainutlaatuiset ominaisuutensa ylikuormitettuina: laiskuus kahdelle ensimmäiselle ja pilkun ensisijaisuus (pilkkujen välinen ilmaisujärjestys on tiukasti määritelty vasen assosiatiiviseksi eli vasemmalta oikealle). Operaattorin ->on palautettava joko osoitin tai objekti (kopiona tai viittauksella).

Operaattoreita voidaan ylikuormittaa sekä itsenäisinä funktioina että luokan jäsenfunktioina. Toisessa tapauksessa operaattorin vasen argumentti on aina *this-objekti. Operaattoreita = -> [] ()voidaan ylikuormittaa vain menetelminä (jäsenfunktioina), ei funktioina.

Voit tehdä koodin kirjoittamisesta paljon helpompaa, jos ylikuormitat operaattorit tietyssä järjestyksessä. Tämä ei vain nopeuttaa kirjoittamista, vaan myös säästää sinua saman koodin kopioimisesta. Tarkastellaan ylikuormitusta käyttämällä esimerkkiä luokasta, joka on geometrinen piste kaksiulotteisessa vektoriavaruudessa:

luokkapiste _ { int x , y ; julkinen : Piste ( int x , int xx ) : x ( x ), y ( xx ) {} // Oletuskonstruktori on poissa. // Rakentajan argumenttien nimet voivat olla samat kuin luokkakenttien nimet. }
  • Kopioi ja siirrä tehtäväoperaattorit operator=
    Kannattaa ottaa huomioon, että C++ luo oletuksena viisi perusfunktiota konstruktorin lisäksi. Siksi osoitusoperaattoreiden kopiointi ja siirtäminen ylikuormitus on parasta jättää kääntäjän tehtäväksi tai toteuttaa käyttämällä Kopioi-ja-vaihto-idiomia .
  • Yhdistetyt aritmeettiset operaattorit += *= -= /= %=jne.
    Jos haluamme toteuttaa tavalliset binääriaritmeettiset operaattorit, on helpompi toteuttaa tämä operaattoriryhmä ensin.Piste & Piste :: operaattori += ( const Piste & rhs ) { x += rhs . x ; y += rhs . y ; palauta * tämä ; }
Operaattori palauttaa arvon viittauksella, jonka avulla voit kirjoittaa tällaisia ​​​​rakenteita:(a += b) += c;
  • Aritmeettiset operaattorit + * - / %
    Päästäksemme eroon koodin toistosta, käytämme yhdistettyä operaattoriamme. Operaattori ei muokkaa objektia, joten se palauttaa uuden objektin.const Piste Piste :: operaattori + ( const Piste & rhs ) const { paluupiste ( * tämä ) + = rhs ; }
Operaattori palauttaa const-arvon. Tämä suojaa meitä kirjoittamasta tällaisia ​​rakenteita (a + b) = c;. Toisaalta luokissa, jotka ovat kalliita kopioida, on paljon kannattavampaa palauttaa arvo ei-vakiokopiosta, eli : MyClass MyClass::operator+(const MyClass& rhs) const;. Tällöin tällaisella tietueella x = y + z;kutsutaan siirtokonstruktoria, ei kopiointikonstruktoria.
  • Unaariset aritmeettiset operaattorit Unaariset + -
    plus- ja miinusoperaattorit eivät ota argumentteja ylikuormitettuina. Ne eivät muuta itse objektia (tapauksessamme), vaan palauttavat uuden muokatun objektin. Sinun tulee myös ylikuormittaa niitä, jos niiden binaariset vastineet ovat ylikuormitettuja.
Piste Piste :: operaattori + () { returnPoint ( * tämä ) ; } Piste Piste :: operaattori - () { point tmp ( * this ); tmp . x *= -1 ; tmp . y *= -1 ; paluu tmp ; }
  • Vertailuoperaattorit == != < <= > >=
    Ensimmäinen asia on ylikuormittaa tasa-arvo- ja epätasa-arvooperaattorit. Epätasa-arvooperaattori käyttää tasa-arvooperaattoria.
bool Piste :: operaattori == ( const Piste & rhs ) const { return ( tämä -> x == rhs . x && tämä -> y == rhs . y ); } bool Piste :: operaattori != ( const Piste & rhs ) const { takaisin ! ( * tämä == rhs ); } Seuraavaksi ylikuormitetaan <- ja >-operaattorit ja sitten niiden ei-tiukat vastineet käyttämällä aiemmin ylikuormitettuja operaattoreita. Geometrian pisteille tällaista toimintoa ei ole määritelty, joten tässä esimerkissä ei ole mitään järkeä ylikuormittaa niitä.
  • Bittioperaattorit <<= >>= &= |= ^= и << >> & | ^ ~
    Niihin sovelletaan samoja periaatteita kuin aritmeettisiin operaattoreihin. Joillakin luokilla bittimaskin käyttö on hyödyllistä std::bitset. Huomaa: &-operaattorilla on yksipuolinen vastine ja sitä käytetään ottamaan osoite; ei yleensä ylikuormitettu.
  • Loogiset operaattorit && ||
    Nämä operaattorit menettävät ainutlaatuiset laiskuusominaisuudet ylikuormitettuina.
  • Lisäys ja vähennys ++ --
    C++ mahdollistaa sekä jälkiliitteen että etuliitteen lisäyksen ja pienentämisen ylikuormituksen. Harkitse lisäystä:
Piste & Piste :: operaattori ++ () { // etuliite x ++ ; y ++ ; palauta * tämä ; } Piste Piste :: operaattori ++ ( int ) { //postfix Piste tmp ( x , y , i ); ++ ( * tämä ); paluu tmp ; } Huomaa, että jäsenfunktion operaattori++(int) ottaa tyypin int arvon, mutta tällä argumentilla ei ole nimeä. C++:lla voit luoda tällaisia ​​toimintoja. Voimme antaa sille (argumentille) nimen ja lisätä pisteiden arvoja tällä tekijällä, mutta operaattorimuodossa tämä argumentti on oletuksena nolla ja sitä voidaan kutsua vain toiminnallisella tyylillä:A.operator++(5);
  • ()-operaattorilla ei ole rajoituksia palautustyypille ja argumenttien tyypeille/määrälle, ja sen avulla voit luoda funktioita .
  • Operaattori, joka välittää luokan tulosvirtaan. Toteutettu erillisenä toimintona, ei jäsentoimintona. Luokassa tämä toiminto on merkitty ystävälliseksi.friend std::ostream& operator<<(const ostream& s, const Point& p);

Muut käyttäjät eivät ole yleisten ylikuormitusohjeiden alaisia.

Kirjoita muunnokset

Tyyppimuunnoksilla voit määrittää säännöt luokkamme muuntamiseksi muihin tyyppeihin ja luokkiin. Voit myös määrittää eksplisiittisen määritteen, joka sallii tyypin muuntamisen vain, jos ohjelmoija on sen erikseen määrittänyt (esim . static_cast<Piste3>(Piste(2,3)); ). Esimerkki:

Piste :: operaattori bool () const { palauta tämä -> x != 0 || tämä -> y != 0 ; } Jako- ja jakeluoperaattorit

Operaattorit new new[] delete delete[]voivat olla ylikuormitettuja ja ne voivat ottaa useita argumentteja. Lisäksi operaattoreiden new и new[]on otettava tyyppiargumentti ensimmäisenä argumenttina std::size_tja palautettava arvo tyyppi void *, ja operaattoreiden on otettava delete delete[]ensimmäinen void *eikä palautettava mitään ( void). Nämä operaattorit voivat olla ylikuormitettuja sekä toimintojen että betoniluokkien osalta.

Esimerkki:

void * MyClass :: operaattori uusi ( std :: size_t s , int a ) { void * p = malloc ( s * a ); if ( p == nullptr ) heittää "Ei vapaata muistia!" ; paluu p ; } // ... // Soita: MyClass * p = uusi ( 12 ) MyClass ;


Mukautetut literaalit

Mukautetut literaalit ovat olleet käytössä yhdestoista C++-standardista lähtien. Literaalit toimivat kuten tavalliset funktiot. Ne voivat olla tekstin sisäisiä tai constexpr-määritteitä . On toivottavaa, että kirjaimellinen alkaa alaviivalla, koska se voi olla ristiriidassa tulevien standardien kanssa. Esimerkiksi literaali i kuuluu jo kompleksilukuihin alkaen std::complex.

Literaalit voivat olla vain yhden seuraavista tyypeistä: const char * , unsigned long long int , long double , char , wchar_t , char16_t , char32_t. Literaalin ylikuormittaminen riittää vain tyypille const char * . Jos sopivaa ehdokasta ei löydy, kutsutaan kyseisen tyyppistä operaattoria. Esimerkki mailien muuntamisesta kilometreiksi:

constexpr int operaattori "" _mi ( unsigned long long int i ) { paluu 1.6 * i ;} constexpr kaksoisoperaattori " " _mi ( pitkä kaksoisoperaattori i ) { paluu 1.6 * i ;}

Merkkijonoliteraaleilla on toinen argumentti std::size_tja yksi ensimmäisistä: const char * , const wchar_t *, const char16_t * , const char32_t *. Merkkijonoliteraalit koskevat lainausmerkeissä olevia merkintöjä.

C++:ssa on sisäänrakennettu etuliitemerkkijono literaali R , joka käsittelee kaikkia lainausmerkkejä tavallisina merkeinä eikä tulkitse tiettyjä sarjoja erikoismerkeiksi. Esimerkiksi tällainen komento std::cout << R"(Hello!\n)"näyttää Hello!\n.

Toteutusesimerkki C#:ssa

Operaattorin ylikuormitus liittyy läheisesti menetelmän ylikuormitukseen. Operaattori on ylikuormitettu avainsanalla Operator, joka määrittelee "operaattorimenetelmän", joka puolestaan ​​määrittelee operaattorin toiminnan suhteessa sen luokkaan. Operaattorimenetelmiä (operaattori) on kahta muotoa : yksi unaarioperaattoreille ja toinen binäärioperaattoreille . Alla on näiden menetelmien kunkin muunnelman yleinen lomake.

// yksipuolisen operaattorin ylikuormituksen yleinen muoto. julkinen staattinen paluutyypin operaattori op ( parametrityyppi operandi ) { // toiminnot } // Binäärioperaattorin ylikuormituksen yleinen muoto. julkinen staattinen paluutyypin operaattori op ( parametrin_tyyppi1 operandi1 , parametrin_tyyppi2 operandi2 ) { // toiminnot }

Tässä "op":n sijaan korvataan ylikuormitettu operaattori, esimerkiksi + tai /; ja "paluutyyppi" tarkoittaa määritetyn toiminnon palauttaman arvon tiettyä tyyppiä. Tämä arvo voi olla mitä tahansa tyyppiä, mutta se määritetään usein olevan samaa tyyppiä kuin luokka, jonka operaattoria ylikuormitetaan. Tämä korrelaatio helpottaa ylikuormitettujen operaattoreiden käyttöä lausekkeissa. Unaarisilla operaattoreilla operandi tarkoittaa välitettävää operandia, ja binäärioperaattoreille samaa merkitään "operandi1 ja operandi2". Huomaa, että operaattorimenetelmien on oltava sekä julkisia että staattisia. Unaarioperaattorien operandityypin on oltava sama kuin luokka, jonka operaattoria ylikuormitetaan. Ja binäärioperaattoreissa ainakin yhden operandin on oltava samaa tyyppiä kuin sen luokka. Siksi C# ei salli operaattoreiden ylikuormittamista objekteihin, joita ei ole vielä luotu. Esimerkiksi +-operaattorin määritystä ei voi ohittaa elementeille, joiden tyyppi on int tai string . Et voi käyttää ref- tai out-muuttujaa operaattoriparametreissa. [yksi]

Vaihtoehdot ja ongelmat

Proseduurien ja toimintojen ylikuormittaminen yleisidean tasolla ei pääsääntöisesti ole vaikea toteuttaa tai ymmärtää. Kuitenkin myös siinä on joitain "sudenkuoppia", jotka on otettava huomioon. Operaattorin ylikuormituksen salliminen aiheuttaa paljon enemmän ongelmia sekä kielen toteuttajalle että tällä kielellä työskentelevälle ohjelmoijalle.

Tunnistusongelma

Ensimmäinen ongelma on kontekstiriippuvuus . Eli ensimmäinen kysymys, jonka prosessien ja toimintojen ylikuormituksen sallivan kielenkääntäjän kehittäjä kohtaa, on: kuinka valita samannimistä menettelyistä se, jota tässä tapauksessa tulisi soveltaa? Kaikki on kunnossa, jos proseduurista on variantti, jonka muodollisten parametrien tyypit vastaavat täsmälleen tässä kutsussa käytettyjen todellisten parametrien tyyppejä. Kuitenkin lähes kaikilla kielillä on jonkin verran vapautta tyyppien käytössä olettaen, että kääntäjä tietyissä tilanteissa muuntaa (lähettää) tietotyypit automaattisesti turvallisesti. Esimerkiksi reaali- ja kokonaislukuargumenttien aritmeettisissa operaatioissa kokonaisluku muunnetaan yleensä reaalityypiksi automaattisesti, ja tulos on todellinen. Oletetaan, että lisäysfunktiosta on kaksi muunnelmaa:

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

Miten kääntäjän tulee käsitellä lauseketta y = add(x, i), jossa x on tyyppiä float ja i on tyyppiä int? Ilmeisesti ei ole tarkkaa vastaavuutta. Vaihtoehtoja on kaksi: joko y=add_int((int)x,i), tai as (tässä funktion ensimmäinen ja toinen versio on merkitty y=add_flt(x, (float)i)nimillä add_intja vastaavasti).add_flt

Herää kysymys: pitäisikö kääntäjän sallia tämä ylikuormitettujen funktioiden käyttö, ja jos, niin millä perusteella se valitsee käytetyn muunnelman? Pitäisikö kääntäjän erityisesti yllä olevassa esimerkissä ottaa huomioon muuttujan y tyyppi valinnassa? On huomattava, että annettu tilanne on yksinkertaisin. Mutta paljon monimutkaisemmat tapaukset ovat mahdollisia, joita pahentaa se, että ei vain sisäänrakennettuja tyyppejä voidaan muuntaa kielen sääntöjen mukaan, vaan myös ohjelmoijan ilmoittamia luokkia, jos niillä on sukulaissuhteita, voidaan lähettää yksi toiselle. Tähän ongelmaan on kaksi ratkaisua:

  • Estä epätarkka tunnistaminen ollenkaan. Vaadi, että jokaiselle tietylle tyyppiparille on olemassa täsmälleen sopiva muunnos ylikuormitetusta menettelystä tai toimenpiteestä. Jos tällaista vaihtoehtoa ei ole, kääntäjän pitäisi antaa virheilmoitus. Ohjelmoijan on tässä tapauksessa käytettävä eksplisiittinen muunnos todellisten parametrien lähettämiseksi haluttuun tyyppisarjaan. Tämä lähestymistapa on hankala kielissä, kuten C++, jotka sallivat melkoisen vapauden käsitellä tyyppejä, koska se johtaa merkittävään eroon sisäänrakennettujen ja ylikuormitettujen operaattoreiden käyttäytymisessä (aritmeettisia operaatioita voidaan soveltaa tavallisiin numeroihin ajattelematta, vaan muihin tyyppeihin - vain nimenomaisella muunnolla) tai valtavan määrän vaihtoehtoja operaatioille.
  • Määritä tietyt säännöt "lähimmän istuvuuden" valitsemiseksi. Yleensä tässä versiossa kääntäjä valitsee varianteista ne, joiden kutsut saadaan lähteestä vain turvallisilla (häviöttömän tiedon) tyyppimuunnoksilla, ja jos niitä on useita, voi valita sen perusteella, kumpi muunnelma vaatii vähemmän. tällaisia ​​muunnoksia. Jos tulos jättää useamman kuin yhden mahdollisuuden, kääntäjä antaa virheen ja vaatii ohjelmoijaa määrittämään muunnelman.
Toiminnan ylikuormituksen erityisongelmat

Toisin kuin menettelyt ja toiminnot, ohjelmointikielten infix-toiminnoilla on kaksi lisäominaisuutta, jotka vaikuttavat merkittävästi niiden toimivuuteen: prioriteetti ja assosiatiivisuus , joiden olemassaolo johtuu mahdollisuudesta "ketjuttaa" operaattoreita (miten ymmärtää a+b*c : miten (a+b)*ctai miten a+(b*c)? Ilmaus a-b+c - tämä (a-b)+cvai a-(b+c)?) .

Kieleen sisäänrakennetuilla operaatioilla on aina ennalta määritelty perinteinen etusija ja assosiaatio. Herää kysymys: mitkä prioriteetit ja assosiatiivisuus ovat näiden operaatioiden uudelleenmääritellyillä versioilla tai lisäksi ohjelmoijan luomilla uusilla operaatioilla? On muitakin yksityiskohtia, jotka saattavat vaatia selvennystä. Esimerkiksi C:ssä on kaksi muotoa lisäys- ja vähennysoperaattoreita ++sekä -- , etuliite ja jälkiliite, jotka toimivat eri tavalla. Miten tällaisten operaattoreiden ylikuormitettujen versioiden pitäisi käyttäytyä?

Eri kielet käsittelevät näitä asioita eri tavoin. Joten C++:ssa operaattorien ylikuormitettujen versioiden ensisijaisuus ja assosiatiivisuus säilyvät samoina kuin kielessä ennalta määritettyjen versioiden, ja lisäys- ja vähennysoperaattoreiden etu- ja jälkiliitemuotojen ylikuormituskuvaukset käyttävät erilaisia ​​allekirjoituksia:

etuliitemuoto Postfix lomake
Toiminto T&operaattori ++(T&) T-operaattori ++(T &, int)
jäsentoiminto T&T::operaattori ++() TT::operaattori ++(int)

Itse asiassa operaatiolla ei ole kokonaislukuparametria - se on kuvitteellinen ja lisätään vain allekirjoitusten muuttamisen vuoksi

Vielä yksi kysymys: onko mahdollista sallia operaattorin ylikuormitus sisäänrakennetuille ja jo ilmoitettuille tietotyypeille? Voiko ohjelmoija muuttaa sisäänrakennetun integraalityypin lisäysoperaation toteutusta? Tai kirjastotyypille "matriisi"? Yleensä ensimmäiseen kysymykseen vastataan kieltävästi. Sisäänrakennettujen tyyppien standarditoimintojen käyttäytymisen muuttaminen on erittäin spesifinen toimenpide, jonka todellinen tarve voi ilmaantua vain harvoissa tapauksissa, kun taas tällaisen ominaisuuden hallitsemattoman käytön haitallisia seurauksia on vaikea edes täysin ennustaa. Siksi kieli yleensä joko kieltää toimintojen uudelleenmäärittelyn sisäänrakennetuille tyypeille tai toteuttaa operaattorin ylikuormitusmekanismin siten, että sen avulla ei yksinkertaisesti voida ohittaa vakiotoimintoja. Mitä tulee toiseen kysymykseen (olemassa oleville tyypeille jo kuvattujen operaattoreiden uudelleenmäärittely), tarvittavat toiminnot tarjoavat täysin luokan periytymismekanismi ja menetelmän ohitus: jos haluat muuttaa olemassa olevan luokan käyttäytymistä, sinun on perittävä se ja määritettävä uudelleen. siinä kuvatut operaattorit. Tässä tapauksessa vanha luokka pysyy ennallaan, uusi saa tarvittavat toiminnot, eikä törmäyksiä tapahdu.

Ilmoitus uusista toiminnoista

Uusien toimintojen julkistamisen tilanne on vielä monimutkaisempi. Tällaisen julistuksen mahdollisuuden sisällyttäminen kieleen ei ole vaikeaa, mutta sen täytäntöönpano on täynnä merkittäviä vaikeuksia. Uuden toiminnon ilmoittaminen on itse asiassa uuden ohjelmointikielen avainsanan luomista, jota vaikeuttaa se, että tekstissä olevat toiminnot voivat pääsääntöisesti seurata ilman erottimia muiden merkkien kanssa. Kun ne ilmestyvät, leksikaalisen analysaattorin järjestämisessä syntyy lisävaikeuksia. Esimerkiksi, jos kielessä on jo operaatiot "+" ja unaari "-" (merkkimuutos), niin lauseke a+-bvoidaan tulkita tarkasti muodossa a + (-b), mutta jos ohjelmassa ilmoitetaan uusi operaatio +-, syntyy heti epäselvyyttä, koska sama lauseke voidaan jo jäsentää ja miten a (+-) b. Kielen kehittäjän ja toteuttajan tulee käsitellä tällaisia ​​ongelmia jollain tavalla. Vaihtoehdot voivat taas olla erilaisia: vaatia, että kaikki uudet toiminnot ovat yksimerkkisiä, oletetaan, että mahdollisten eroavaisuuksien sattuessa valitaan operaation "pisin" versio (eli kunnes seuraava merkkisarja, jonka lukee kääntäjä vastaa mitä tahansa toimintoa, sen lukeminen jatkuu), yritä havaita törmäykset käännöksen aikana ja tuottaa virheitä kiistanalaisissa tapauksissa ... Tavalla tai toisella kielet, jotka mahdollistavat uusien toimintojen ilmoittamisen, ratkaisevat nämä ongelmat.

Ei pidä unohtaa, että uusien toimintojen yhteydessä on myös kysymys assosiatiivisuuden ja prioriteetin määrittämisestä. Vakiokielioperaation muodossa ei ole enää valmiita ratkaisuja, ja yleensä sinun on vain asetettava nämä parametrit kielen säännöillä. Tee esimerkiksi kaikista uusista operaatioista vasen-assosiatiivisia ja anna niille sama, kiinteä, prioriteetti tai ota käyttöön kieleen keino molempien määrittämiseen.

Ylikuormitus ja polymorfiset muuttujat

Kun ylikuormitettuja operaattoreita, toimintoja ja proseduureja käytetään vahvasti kirjoitetuissa kielissä, joissa jokaisella muuttujalla on ennalta määritetty tyyppi, kääntäjä päättää, mitä ylikuormitetun operaattorin versiota käyttää kussakin tapauksessa, olipa se kuinka monimutkainen tahansa. . Tämä tarkoittaa, että käännetyillä kielillä operaattorin ylikuormituksen käyttö ei heikennä suorituskykyä millään tavalla - joka tapauksessa ohjelman objektikoodissa on hyvin määritelty operaatio tai funktiokutsu. Tilanne on erilainen, kun kielessä on mahdollista käyttää polymorfisia muuttujia - muuttujia, jotka voivat sisältää eri tyyppisiä arvoja eri aikoina.

Koska arvon tyyppi, johon ylikuormitettu operaatio sovelletaan, ei ole tiedossa koodin käännöshetkellä, kääntäjä ei voi valita haluttua vaihtoehtoa etukäteen. Tässä tilanteessa objektikoodiin on pakko upottaa fragmentti, joka välittömästi ennen tämän toiminnon suorittamista määrittää argumenttien arvojen tyypit ja valitsee dynaamisesti tätä tyyppijoukkoa vastaavan muunnelman. Lisäksi tällainen määritelmä on tehtävä joka kerta, kun operaatio suoritetaan, koska jopa sama koodi, joka kutsutaan toisen kerran, voidaan suorittaa eri tavalla ...

Siten operaattorin ylikuormituksen käyttö yhdessä polymorfisten muuttujien kanssa tekee väistämättömäksi dynaamisesti määrittää, mitä koodia kutsutaan.

Kritiikki

Kaikki asiantuntijat eivät pidä ylikuormituksen käyttöä siunauksena. Jos toimintojen ja toimintojen ylikuormitus ei yleensä löydä vakavia vastalauseita (osittain siksi, että se ei johda joihinkin tyypillisiin "operaattorin" ongelmiin, osittain siksi, että sen väärinkäyttö on vähemmän houkuttelevaa), niin operaattorin ylikuormitus, kuten periaatteessa, ja erityisesti kielitoteutuksia, on useiden ohjelmointiteoreetikojen ja -harjoittajien kohtaama melko ankara kritiikki.

Kriitikot huomauttavat, että yllä kuvatut identifiointi-, etusija- ja assosiatiivisuusongelmat tekevät usein ylikuormitettujen operaattoreiden käsittelystä tarpeettoman vaikeaa tai luonnotonta:

  • Henkilöllisyystodistus. Jos kielellä on tiukat tunnistussäännöt, ohjelmoijan on pakko muistaa, mille tyyppiyhdistelmille on ylikuormitettuja operaatioita ja lähettää niihin manuaalisesti operandeja. Jos kieli sallii "likimääräisen" tunnistamisen, ei koskaan voi olla varma, että jossain melko monimutkaisessa tilanteessa suoritetaan juuri se toiminnon variantti, jonka ohjelmoija oli mielessään.
    • Operaation "ylikuormitus" tietylle tyypille on helppo määrittää, jos kieli tukee periytymistä tai rajapintoja ( tyyppiluokkia ). Jos kieli ei salli tätä, se on suunnitteluongelma. Joten OOP-kielissä ( Java , C# ) menetelmäoperaattorit peritään Objectvastaavista luokista (vertailu, numeeriset operaatiot, bittikohtaiset jne.) tai ennalta määritellyistä liitännöistä.
    • "Likimääräinen tunnistus" on olemassa vain kielillä, joissa on löysä tyyppijärjestelmä, jossa " kyky ampua itseäsi jalkaan " "melko vaikeassa tilanteessa" on olemassa pysyvästi ja ilman kuljettajan ylikuormitusta.
  • Prioriteetti ja assosiaatio. Jos ne on määritelty tiukasti, tämä voi olla hankalaa eikä aihepiiriin liittyvää (esimerkiksi joukkooperaatioissa prioriteetit poikkeavat aritmeettisista). Jos ohjelmoija voi asettaa ne, tästä tulee ylimääräinen virhegeneraattori (jos vain siksi, että yhden operaation eri muunnelmilla osoittautuu olevan erilaiset prioriteetit tai jopa assosiatiivisuus).
    • Tämä ongelma on osittain ratkaistu määrittelemällä uusia operaattoreita (esimerkiksi \/sekä disjunktiolle/\ että konjunktiolle ) .

Se, kuinka paljon omien toimintojen käyttömukavuus voi painaa ohjelman hallittavuuden heikkenemisestä aiheutuvan haitan, on kysymys, johon ei ole yksiselitteistä vastausta.

Jotkut kriitikot vastustavat ylikuormitusta ohjelmistokehityksen teorian yleisten periaatteiden ja todellisen teollisen käytännön perusteella.

  • "Puritaanisen" lähestymistavan kannattajat kielten rakentamiseen, kuten Wirth tai Hoare , vastustavat operaattorin ylikuormitusta yksinkertaisesti siksi, että sitä väitetään olevan helppo tehdä ilman. Heidän mielestään tällaiset työkalut vain vaikeuttavat kieltä ja kääntäjää tarjoamatta tätä monimutkaista lisäominaisuuksia. Heidän mielestään ajatus tehtäväkeskeisen kielen laajennuksen luomisesta näyttää vain houkuttelevalta. Todellisuudessa kielenlaajennustyökalujen käyttö tekee ohjelman ymmärrettävän vain sen tekijälle - tämän laajennuksen kehittäjälle. Ohjelmasta tulee paljon vaikeampi muiden ohjelmoijien ymmärtää ja analysoida, mikä tekee ylläpidosta, muokkaamisesta ja tiimin kehittämisestä vaikeampaa.
  • On huomattava, että itse ylikuormituksen käyttömahdollisuus on usein provosoiva rooli: ohjelmoijat alkavat käyttää sitä aina kun mahdollista, minkä seurauksena työkalusta, joka on suunniteltu yksinkertaistamaan ja virtaviivaistamaan ohjelmaa, tulee sen liiallisen monimutkaisuuden ja hämmennyksen syy.
  • Ylikuormitetut operaattorit eivät välttämättä tee juuri sitä, mitä heiltä odotetaan lajinsa perusteella. Esimerkiksi a + bse yleensä (mutta ei aina) tarkoittaa samaa kuin b + amutta «один» + «два»eroaa «два» + «один»kielistä, joissa operaattori +on ylikuormitettu merkkijonojen yhdistämiseen .
  • Operaattorin ylikuormitus tekee ohjelmafragmenteista kontekstiherkempiä. Tietämättä lausekkeen operandityyppejä, on mahdotonta ymmärtää, mitä lauseke tekee, jos se käyttää ylikuormitettuja operaattoreita. Esimerkiksi C++- ohjelmassa operaattori <<voi tarkoittaa sekä bittikohtaista siirtoa, ulostuloa streamiin että merkkien siirtymistä merkkijonossa tietyllä määrällä paikkoja. Lauseke a << 1palauttaa:
    • tulos arvon abittisiirrosta yhden bitin vasemmalle, jos aon kokonaisluku;
    • jos a - merkkijono, niin tuloksena on merkkijono, jonka loppuun on lisätty yksi välilyöntimerkki (merkki kerrallaan siirtyy 1 pisteen verran vasemmalle), ja eri tietokonejärjestelmissä välilyönnin koodi voivat vaihdella;
    • mutta jos aon lähtövirta , sama lauseke tulostaa numeron 1 kyseiseen virtaan «1».

Tämä ongelma on luonnollisesti seurausta kahdesta edellisestä. Se on helppo tasoittaa sopimusten hyväksymisellä ja yleisellä ohjelmointikulttuurilla.

Luokitus

Seuraavassa on joidenkin ohjelmointikielten luokitus sen mukaan, sallivatko ne operaattorin ylikuormituksen ja ovatko operaattorit rajoitettu ennalta määritettyyn joukkoon:

Monet
Operaattorit

Ei ylikuormitusta

On ylikuormitusta
Vain
ennalta määritetty

C
Java
JavaScript
Objective-C
Pascal
PHP
ActionScript
Go

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

Groovy

On mahdollista
ottaa käyttöön uusia

ML
Pico
Lisp

Algol 68
Fortran
Haskell
PostgreSQL
Prologue
Perl 6
Seed7
Smalltalk
Julia

Muistiinpanot

  1. Herbert Schildt. Täydellinen opas C# 4.0:aan, 2011.

Katso myös