Přetížení operátora

Aktuální verze stránky ještě nebyla zkontrolována zkušenými přispěvateli a může se výrazně lišit od verze recenzované 9. července 2018; kontroly vyžadují 25 úprav .

Přetěžování operátorů v programování  je jedním ze způsobů, jak implementovat polymorfismus , který spočívá v možnosti současné existence několika různých možností ve stejném rozsahu pro použití operátorů, které mají stejné jméno, ale liší se v typech parametrů, ke kterým jsou přiřazeny. aplikovaný.

Terminologie

Termín " přetížení " je pauzovací papír z anglického slova přetížení . Takový překlad se objevil v knihách o programovacích jazycích v první polovině devadesátých let. V publikacích sovětského období se podobné mechanismy nazývaly redefinice nebo redefinice , překrývající se operace.

Důvody pro

Někdy je potřeba popsat a aplikovat operace na datové typy vytvořené programátorem, které jsou svým významem ekvivalentní těm, které jsou již v daném jazyce k dispozici. Klasickým příkladem je knihovna pro práci s komplexními čísly . Stejně jako běžné číselné typy podporují aritmetické operace a bylo by přirozené vytvořit pro tento typ operace „plus“, „mínus“, „násobit“, „dělit“ a označovat je stejnými znaménky operací jako pro jiné číselné typy. Zákaz používání prvků definovaných v jazyce si vynucuje vytvoření mnoha funkcí s názvy jako ComplexPlusComplex, IntegerPlusComplex, ComplexMinusFloat a tak dále.

Když jsou operace stejného významu aplikovány na operandy různých typů, jsou nuceny být pojmenovány odlišně. Nemožnost používat funkce se stejným názvem pro různé typy funkcí vede k nutnosti vymýšlet různé názvy pro stejnou věc, což vytváří zmatek a může dokonce vést k chybám. Například v klasickém jazyce C existují dvě verze standardní knihovní funkce pro zjištění modulu čísla: abs() a fabs() - první je pro celočíselný argument, druhá pro skutečný. Tato situace v kombinaci se slabou kontrolou typu C může vést k těžko dohledatelné chybě: pokud programátor do výpočtu zapíše abs(x), kde x je skutečná proměnná, pak některé kompilátory bez varování vygenerují kód, který převeďte x na celé číslo vyřazením zlomkových částí a z výsledného celého čísla vypočítejte modul.

Částečně je problém vyřešen pomocí objektového programování - když jsou nové datové typy deklarovány jako třídy, lze operace s nimi formalizovat jako metody třídy, včetně metod třídy stejného jména (protože metody různých tříd nemusí mít různá jména), ale za prvé je takový způsob návrhu operací s hodnotami různých typů nepohodlný a za druhé neřeší problém vytváření nových operátorů.

Nástroje, které umožňují jazyk rozšiřovat, doplňovat o nové operace a syntaktické konstrukce (a přetěžování operací je jedním z takových nástrojů, spolu s objekty, makry, funkcionály, uzávěry) z něj dělají metajazyk  - nástroj pro popis jazyků zaměřené na konkrétní úkoly. S jeho pomocí je možné pro každý konkrétní úkol sestavit jazykové rozšíření, které je pro něj nejvhodnější, což umožní popsat jeho řešení co nejpřirozenější, nejsrozumitelnější a nejjednodušší formou. Například v aplikaci na přetěžování operací: vytvoření knihovny složitých matematických typů (vektory, matice) a popis operací s nimi v přirozené, „matematické“ podobě, vytváří „jazyk pro vektorové operace“, ve kterém je složitost výpočty jsou skryté a je možné popisovat řešení úloh pomocí vektorových a maticových operací se zaměřením na podstatu problému, nikoli na techniku. Z těchto důvodů byly takové prostředky kdysi zahrnuty do jazyka Algol-68 .

Mechanismus přetížení

Implementace

Přetěžování operátorů zahrnuje zavedení dvou vzájemně souvisejících funkcí do jazyka: schopnost deklarovat několik procedur nebo funkcí se stejným názvem ve stejném rozsahu a schopnost popsat vlastní implementace binárních operátorů (tj. znaky operací, obvykle zapsané v infixové notaci, mezi operandy). V zásadě je jejich implementace poměrně jednoduchá:

Přetížení operátorů v C++

V C++ existují čtyři typy přetížení operátorů:

  1. Přetížení běžných operátorů + - * / % ˆ & | ~ ! = < > += -= *= /= %= ˆ= &= |= << >> >>= <<= == != <= >= && || ++ -- , ->* -> ( ) <=> [ ]
  2. Přetížení operátorů konverze typu
  3. Přetížení operátorů '''new''' alokace a '''delete''' pro objekty v paměti.
  4. Literály operátora přetížení
Obyčejné operátory

Je důležité si pamatovat, že přetížení vylepšuje jazyk, nemění jazyk, takže nemůžete přetěžovat operátory pro vestavěné typy. Nelze změnit prioritu a asociativitu (zleva doprava nebo zprava doleva) operátorů. Nemůžete si vytvořit vlastní operátory a přetížit některé vestavěné: :: . .* ?: sizeof typeid. Operátory také && || ,při přetížení ztrácejí své jedinečné vlastnosti: lenost u prvních dvou a přednost u čárky (pořadí výrazů mezi čárkami je striktně definováno jako zleva asociativní, tedy zleva doprava). Operátor ->musí vrátit buď ukazatel nebo objekt (kopírováním nebo odkazem).

Operátory mohou být přetíženy jako samostatné funkce i jako členské funkce třídy. Ve druhém případě je levým argumentem operátoru vždy *tento objekt. Operátory = -> [] ()lze přetížit pouze jako metody (členské funkce), nikoli jako funkce.

Psaní kódu si můžete značně usnadnit, pokud přetížíte operátory v určitém pořadí. To nejen zrychlí zápis, ale také vás ušetří duplikování stejného kódu. Uvažujme přetížení na příkladu třídy, která je geometrickým bodem ve dvourozměrném vektorovém prostoru:

classPoint _ { int x , y ; veřejnost : Bod ( int x , int xx ) : x ( x ), y ( xx ) {} // Výchozí konstruktor je pryč. // Názvy argumentů konstruktoru mohou být stejné jako názvy polí tříd. }
  • Kopírovat a přesunout operátory přiřazení operator=
    Stojí za zvážení, že standardně C++ vytváří kromě konstruktoru pět základních funkcí. Přetížení operátorů přiřazení kopírováním a přesunem je proto nejlepší ponechat na kompilátoru nebo je implementovat pomocí idiomu Copy-and-swap .
  • Kombinované aritmetické operátory += *= -= /= %=atd.
    Pokud chceme implementovat běžné binární aritmetické operátory, bude výhodnější implementovat nejprve tuto skupinu operátorů.Point & Point :: operator += ( const Point & rhs ) { x += rhs . x ; y + = rhs . y ; vrátit * toto ; }
Operátor vrací hodnotu odkazem, což vám umožňuje psát takové konstrukce:(a += b) += c;
  • Aritmetické operátory + * - / %
    Abychom se zbavili opakování kódu, použijme náš kombinovaný operátor. Operátor objekt neupravuje, takže vrací nový objekt.const Point Point :: operátor + ( const Point & rhs ) const { návratový bod ( * toto ) += rhs ; }
Operátor vrací konstantní hodnotu. To nás ochrání před psaním konstrukcí tohoto druhu (a + b) = c;. Na druhou stranu pro třídy, jejichž kopírování je drahé, je mnohem výhodnější vrátit hodnotu z nekonstantní kopie, tedy : MyClass MyClass::operator+(const MyClass& rhs) const;. Potom se s takovým záznamem x = y + z;zavolá konstruktor přesunu, nikoli konstruktor kopírování.
  • Unární aritmetické operátory Jednotné operátory + -
    plus a mínus neberou při přetížení žádné argumenty. Nezmění samotný objekt (v našem případě), ale vrátí nový upravený objekt. Měli byste je také přetížit, pokud jsou přetíženy jejich binární protějšky.
Bod Bod :: operátor + () { returnPoint ( * this ) ; } Point Point :: operator - () { bod tmp ( * toto ); tmp . x *= -1 ; tmp . y *= -1 ; return tmp ; }
  • Porovnávací operátory == != < <= > >=
    První věc, kterou musíte udělat, je přetížit operátory rovnosti a nerovnosti. Operátor nerovnosti použije operátor rovnosti.
bool Bod :: operátor == ( const Point & rhs ) const { return ( this -> x == rhs . x && this -> y == rhs . y ); } bool Bod :: operátor != ( const Point & rhs ) const { vrátit se ! ( * toto == rhs ); } Dále jsou přetíženy operátory < a > a poté jejich nepřísné protějšky pomocí dříve přetížených operátorů. Pro body v geometrii není taková operace definována, takže v tomto příkladu nemá smysl je přetěžovat.
  • Bitové operátory <<= >>= &= |= ^= и << >> & | ^ ~
    Podléhají stejným principům jako aritmetické operátory. V některých třídách se vám bude hodit bitová maska std::bitset. Poznámka: Operátor & má unární protějšek a používá se k převzetí adresy; obvykle není přetížen.
  • Logické operátory Tyto operátory při přetížení && ||
    ztratí své jedinečné vlastnosti lenosti .
  • Inkrementace a dekrementace ++ --
    C++ vám umožňuje přetížit jak postfix, tak prefix inkrementace a dekrementace. Zvažte zvýšení:
Bod & Bod :: operátor ++ () { // prefix x ++ ; y ++ ; vrátit * toto ; } Bod Bod :: operátor ++ ( int ) { //postfix Bod tmp ( x , y , i ); ++ ( * toto ); return tmp ; } Všimněte si, že operátor členské funkce++(int) má hodnotu typu int, ale tento argument nemá jméno. C++ vám umožňuje vytvářet takové funkce. Můžeme mu (argumentu) dát jméno a zvýšit hodnoty bodů o tento faktor, ale ve formě operátoru bude tento argument výchozí nula a lze jej volat pouze ve funkčním stylu:A.operator++(5);
  • Operátor () nemá žádná omezení na návratový typ a typy/počet argumentů a umožňuje vytvářet funktory .
  • Operátor, který předá třídu výstupnímu proudu. Implementováno jako samostatná funkce, nikoli jako členská funkce. Ve třídě je tato funkce označena jako přátelská.friend std::ostream& operator<<(const ostream& s, const Point& p);

Ostatní operátoři nepodléhají žádným obecným směrnicím o přetížení.

Typ konverze

Převody typů umožňují určit pravidla pro převod naší třídy na jiné typy a třídy. Můžete také zadat explicitní specifikátor, který povolí převod typu pouze v případě, že jej programátor výslovně určil (například static_cast<Point3>(Point(2,3)); ). Příklad:

Bod :: operátor bool () const { vrátit toto -> x != 0 || toto -> y != 0 ; } Alokační a dealokační operátoři

Operátory new new[] delete delete[]mohou být přetížené a mohou převzít libovolný počet argumentů. Navíc new и new[]musí operátory vzít argument typu jako první argument std::size_ta vrátit hodnotu typu void *a operátory musí vzít delete delete[]první void *a nevrátit nic ( void). Tyto operátory mohou být přetíženy jak pro funkce, tak pro konkrétní třídy.

Příklad:

void * MyClass :: operátor new ( std :: size_t s , int a ) { void * p = malloc ( s * a ); if ( p == nullptr ) hodit "Žádná volná paměť!" ; vrátit p ; } // ... // Volání: MyClass * p = new ( 12 ) MyClass ;


Vlastní literály

Vlastní literály existují již od jedenáctého standardu C++. Literály se chovají jako běžné funkce. Mohou to být inline nebo constexpr kvalifikátory . Je žádoucí, aby doslovný text začínal znakem podtržení, protože by mohlo dojít ke konfliktu s budoucími standardy. Například literál i již patří ke komplexním číslům z std::complex.

Literály mohou mít pouze jeden z následujících typů: const char * , unsigned long long int , long double , char , wchar_t , char16_t , char32_t. Literál stačí přetížit pouze pro typ const char * . Pokud nebude nalezen žádný vhodnější kandidát, bude zavolán operátor tohoto typu. Příklad převodu mil na kilometry:

constexpr int operátor "" _mi ( bez znaménka long long int i ) { return 1,6 * i ;} constexpr dvojitý operátor "" _mi ( dlouhé dvojité i ) { return 1,6 * i ;}

Řetězcové literály mají druhý argument std::size_ta jeden z prvních: const char * , const wchar_t *, const char16_t * , const char32_t *. Řetězcové literály se vztahují na položky v uvozovkách.

C++ má vestavěný předponový řetězcový literál R , který zachází se všemi znaky v uvozovkách jako s běžnými znaky a neinterpretuje určité sekvence jako speciální znaky. Takový příkaz například std::cout << R"(Hello!\n)"zobrazí Hello!\n.

Příklad implementace v C#

Přetížení operátora úzce souvisí s přetížením metody. Operátor je přetížen klíčovým slovem Operator, které definuje "metodu operátora", která zase definuje akci operátora s ohledem na jeho třídu. Existují dvě formy operátorových metod (operátor): jedna pro unární operátory , druhá pro binární operátory . Níže je uveden obecný formulář pro každou variantu těchto metod.

// obecná forma přetížení unárního operátora. public static operátor typu return_type op ( operand typu_parametru ) { // operace } // Obecná forma přetížení binárního operátoru. public static return_type operator op ( parametr_type1 operand1 , parameter_type2 operand2 ) { // operations }

Zde je místo "op" nahrazen přetížený operátor, například + nebo /; a "return_type" označuje konkrétní typ hodnoty vrácené zadanou operací. Tato hodnota může být libovolného typu, ale často se uvádí, že je stejného typu jako třída, pro kterou je operátor přetížen. Tato korelace usnadňuje použití přetížených operátorů ve výrazech. U unárních operátorů operand označuje předávaný operand a u binárních operátorů se totéž označuje jako "operand1 a operand2". Všimněte si, že operátorské metody musí být obou typů, veřejné i statické. Typ operandu unárních operátorů musí být stejný jako třída, pro kterou je operátor přetěžován. A v binárních operátorech musí být alespoň jeden z operandů stejného typu jako jeho třída. C# tedy neumožňuje přetěžování žádných operátorů na dosud nevytvořených objektech. Například přiřazení operátoru + nelze přepsat u prvků typu int nebo string . V parametrech operátora nemůžete použít modifikátor ref nebo out. [jeden]

Možnosti a problémy

Přetěžování postupů a funkcí na úrovni obecné myšlenky zpravidla není obtížné ani implementovat, ani pochopit. I v ní však existují některá „úskalí“, která je třeba vzít v úvahu. Povolení přetížení operátorů vytváří mnohem více problémů jak pro implementátor jazyka, tak pro programátora pracujícího v tomto jazyce.

Problém s identifikací

Prvním problémem je kontextová závislost . Tedy první otázka, se kterou se vývojář jazykového překladače, který umožňuje přetěžování procedur a funkcí, potýká: jak vybrat ze stejnojmenných procedur tu, která by měla být aplikována v tomto konkrétním případě? Vše je v pořádku, pokud existuje varianta postupu, jejíž typy formálních parametrů přesně odpovídají typům skutečných parametrů použitých v tomto volání. Téměř ve všech jazycích však existuje určitá míra volnosti v použití typů za předpokladu, že kompilátor v určitých situacích automaticky bezpečně převádí (přetypuje) datové typy. Například v aritmetických operacích s reálnými a celočíselnými argumenty se celé číslo obvykle automaticky převede na reálný typ a výsledek je reálný. Předpokládejme, že existují dvě varianty funkce add:

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

Jak by měl kompilátor zacházet s výrazem y = add(x, i), kde x je typu float a i je typu int? Je zřejmé, že neexistuje přesná shoda. Existují dvě možnosti: buď y=add_int((int)x,i), nebo jako (zde jsou první a druhá verze funkce označeny y=add_flt(x, (float)i)jmény add_intresp .).add_flt

Nabízí se otázka: má překladač umožnit toto použití přetížených funkcí, a pokud ano, na základě čeho zvolí konkrétní použitou variantu? Zejména ve výše uvedeném příkladu by měl překladatel při výběru zvážit typ proměnné y? Nutno podotknout, že daná situace je nejjednodušší. Jsou ale možné mnohem komplikovanější případy, které jsou ztíženy tím, že nejen vestavěné typy lze převádět podle pravidel jazyka, ale také třídy deklarované programátorem, pokud mají příbuzenské vztahy, lze z jeden druhému. Existují dvě řešení tohoto problému:

  • Zakázat nepřesnou identifikaci vůbec. Požadujte, aby pro každou konkrétní dvojici typů existovala přesně vhodná varianta přetíženého postupu nebo operace. Pokud taková možnost neexistuje, kompilátor by měl vyvolat chybu. Programátor v tomto případě musí použít explicitní převod, aby přetypoval skutečné parametry na požadovanou sadu typů. Tento přístup je nepohodlný v jazycích, jako je C++, které umožňují značnou volnost při práci s typy, protože vede k výraznému rozdílu v chování vestavěných a přetížených operátorů (aritmetické operace lze aplikovat na běžná čísla bez přemýšlení, ale na jiné typy - pouze s výslovnou konverzí) nebo ke vzniku obrovského množství možností operací.
  • Stanovte si určitá pravidla pro výběr toho „nejbližšího“. Překladač obvykle v této variantě volí ty z variant, jejichž volání lze ze zdroje získat pouze převodem typu bezpečné (neztrátové informace), a pokud jich je více, může si vybrat podle toho, která varianta vyžaduje méně takové konverze. Pokud výsledek ponechává více než jednu možnost, kompilátor vyvolá chybu a vyžaduje, aby programátor explicitně specifikoval variantu.
Specifické problémy s přetížením operace

Na rozdíl od procedur a funkcí mají infixové operace programovacích jazyků dvě další vlastnosti, které významně ovlivňují jejich funkčnost: prioritu a asociativitu , jejichž přítomnost je způsobena možností „řetězového“ záznamu operátorů (jak rozumět a+b*c : jak (a+b)*cnebo jak a+(b*c)Výraz a-b+c – toto (a-b)+cnebo a-(b+c)?).

Operace zabudované do jazyka mají vždy předem definovanou tradiční prioritu a asociativitu. Nabízí se otázka: jaké priority a asociativitu budou mít předefinované verze těchto operací, nebo navíc nové operace vytvořené programátorem? Existují další jemnosti, které mohou vyžadovat objasnění. Například v C existují dvě formy operátorů inkrementace a dekrementace ++a -- , prefix a postfix, které se chovají odlišně. Jak se mají chovat přetížené verze takových operátorů?

Různé jazyky řeší tyto problémy různými způsoby. Takže v C++ je priorita a asociativita přetížených verzí operátorů zachována stejně jako u předdefinovaných operátorů v jazyce a přetížené popisy prefixových a postfixových forem inkrementačních a dekrementačních operátorů používají různé signatury:

předponový formulář Postfixový formulář
Funkce T&operátor ++(T&) Operátor T ++(T &, int)
členská funkce T&T::operátor ++() TT::operátor ++(int)

Operace ve skutečnosti nemá celočíselný parametr – je fiktivní a přidává se pouze proto, aby se změnily podpisy

Ještě jeden dotaz: je možné povolit přetěžování operátorů pro vestavěné a pro již deklarované datové typy? Může programátor změnit implementaci operace sčítání pro vestavěný celočíselný typ? Nebo pro typ knihovny "matrix"? Na první otázku se zpravidla odpovídá záporně. Změna chování standardních operací u vestavěných typů je extrémně specifická akce, jejíž skutečná potřeba může nastat jen ve vzácných případech, přičemž škodlivé důsledky nekontrolovaného používání takové funkce je obtížné dokonce plně předvídat. Jazyk proto obvykle buď zakazuje předefinování operací pro vestavěné typy, nebo implementuje mechanismus přetěžování operátorů takovým způsobem, že standardní operace s jeho pomocí prostě nelze přepsat. Pokud jde o druhou otázku (předefinování operátorů již popsaných pro existující typy), potřebnou funkcionalitu plně zajišťuje mechanismus dědičnosti tříd a přepisování metody: pokud chcete změnit chování existující třídy, musíte ji zdědit a předefinovat operátory v něm popsané. V tomto případě zůstane stará třída nezměněna, nová získá potřebnou funkčnost a nedojde ke kolizi.

Oznámení o nových operacích

Ještě složitější je situace s vyhlašováním nových provozů. Zahrnout možnost takového prohlášení v jazyce není obtížné, ale jeho implementace je plná značných potíží. Deklarace nové operace je ve skutečnosti vytvořením nového klíčového slova programovacího jazyka, což je komplikované tím, že operace v textu mohou zpravidla následovat bez oddělovačů s jinými tokeny. Když se objeví, vyvstanou další potíže v organizaci lexikálního analyzátoru. Například, pokud jazyk již má operace „+“ a unární „-“ (změna znaménka), pak lze výraz a+-bpřesně interpretovat jako a + (-b), ale pokud je v programu deklarována nová operace +-, okamžitě vzniká nejednoznačnost, protože stejný výraz již lze analyzovat a jak a (+-) b. Vývojář a implementátor jazyka se s takovými problémy musí nějakým způsobem vypořádat. Možnosti mohou být opět různé: požadovat, aby všechny nové operace byly jednoznakové, předpokládejte, že v případě jakýchkoliv nesrovnalostí bude vybrána „nejdelší“ verze operace (tj. dokud nebude další sada znaků přečtena překladač odpovídá jakékoli operaci, pokračuje ve čtení), snažte se detekovat kolize při překladu a generovat chyby v kontroverzních případech... Tak či onak jazyky, které umožňují deklaraci nových operací, tyto problémy řeší.

Nemělo by se zapomínat, že u nových operací je zde také otázka určování asociativnosti a priority. Již neexistuje hotové řešení v podobě standardní jazykové operace a většinou stačí tyto parametry nastavit s pravidly daného jazyka. Například udělejte ze všech nových operací levý asociativní a dejte jim stejnou, pevnou, prioritu nebo zaveďte do jazyka prostředky pro určení obou.

Přetížení a polymorfní proměnné

Když se přetížené operátory, funkce a procedury používají v silně typizovaných jazycích, kde má každá proměnná předem deklarovaný typ, je na kompilátoru, aby rozhodl, kterou verzi přetíženého operátoru v každém konkrétním případě použít, bez ohledu na to, jak složitý je. . To znamená, že u kompilovaných jazyků použití přetěžování operátorů nijak nesnižuje výkon – v každém případě je v objektovém kódu programu dobře definovaná operace nebo volání funkce. Jiná situace je, když je možné v jazyce použít polymorfní proměnné - proměnné, které mohou obsahovat hodnoty různých typů v různých časech.

Vzhledem k tomu, že typ hodnoty, na kterou bude přetížená operace aplikována, není v době překladu kódu znám, je kompilátor připraven o možnost vybrat si předem požadovanou možnost. V této situaci je nuceno vložit fragment do kódu objektu, který bezprostředně před provedením této operace určí typy hodnot v argumentech a dynamicky vybere variantu odpovídající této sadě typů. Navíc taková definice musí být provedena pokaždé, když je operace provedena, protože i tentýž kód, který je volán podruhé, může být dobře proveden odlišně ...

Použití přetěžování operátorů v kombinaci s polymorfními proměnnými tedy činí nevyhnutelné dynamicky určovat, který kód se má volat.

Kritika

Použití přetížení není všemi odborníky považováno za dobrodiní. Pokud přetěžování funkcí a procedur obecně nenachází žádné závažné námitky (částečně proto, že nevede k některým typickým „provozovatelským“ problémům, částečně proto, že je méně lákavé je zneužít), pak přetěžování operátorů, jako v zásadě, a konkrétně implementace jazyka, je vystaven poměrně ostré kritice ze strany mnoha programátorských teoretiků a praktiků.

Kritici poukazují na to, že problémy s identifikací, prioritou a asociativitou nastíněné výše často činí jednání s přetíženými operátory buď zbytečně obtížné, nebo nepřirozené:

  • Identifikace. Pokud má jazyk přísná identifikační pravidla, pak je programátor nucen si pamatovat, pro které kombinace typů existují přetížené operace a ručně do nich přetypovat operandy. Pokud jazyk umožňuje "přibližnou" identifikaci, člověk si nikdy nemůže být jistý, že v nějaké dost komplikované situaci bude provedena přesně ta varianta operace, kterou měl programátor na mysli.
    • "Přetížení" operace pro určitý typ lze snadno určit, pokud jazyk podporuje dědičnost nebo rozhraní ( třídy typů ). Pokud to jazyk neumožňuje, je to problém designu. Takže v jazycích OOP ​​ ( Java , C# ) jsou operátory metod zděděny z Objecta nikoli z odpovídajících tříd (porovnání, numerické operace, bitové atd.) nebo předdefinovaných rozhraní.
    • "Přibližná identifikace" existuje pouze v jazycích s volným typem systému, kde " schopnost střelit se do nohy " "v poměrně obtížné situaci" je trvale přítomná a bez přetěžování operátora.
  • Priorita a asociativita. Pokud jsou pevně definovány, může to být nepohodlné a nerelevantní pro danou oblast (například u operací s množinami se priority liší od aritmetických). Pokud je může nastavit programátor, stává se to dalším generátorem chyb (už jen proto, že různé varianty jedné operace mají různé priority nebo dokonce asociativitu).
    • Tento problém je částečně vyřešen definicí nových operátorů (například \/pro disjunkci/\ i konjunkci ) .

Nakolik může pohodlí používání vlastních operací převážit nepohodlí se zhoršenou ovladatelností programu, to je otázka, která nemá jednoznačnou odpověď.

Někteří kritici hovoří proti přetěžování operací, založených na obecných principech teorie vývoje softwaru a skutečné průmyslové praxe.

  • Zastánci „puritánského“ přístupu ke konstrukci jazyků, jako je Wirth nebo Hoare , se staví proti přetěžování operátorů jednoduše proto, že se bez něj údajně snadno obejdete. Podle jejich názoru takové nástroje pouze komplikují jazyk a překladač, aniž by poskytovaly další funkce odpovídající této komplikaci. Podle jejich názoru samotná myšlenka vytvoření rozšíření jazyka zaměřeného na úkoly vypadá pouze atraktivně. Ve skutečnosti použití nástrojů pro rozšíření jazyka činí program srozumitelným pouze jeho autorovi - tomu, kdo toto rozšíření vyvinul. Program se stává mnohem obtížnějším pro ostatní programátory pochopit a analyzovat, což ztěžuje údržbu, úpravy a vývoj týmu.
  • Je třeba poznamenat, že samotná možnost použití přetížení často hraje provokující roli: programátoři jej začínají používat všude, kde je to možné, v důsledku toho se nástroj určený ke zjednodušení a zefektivnění programu stává příčinou jeho nadměrné komplikace a zmatku.
  • Přetížení operátoři nemusí dělat přesně to, co se od nich na základě jejich druhu očekává. Například a + bobvykle (ale ne vždy) znamená totéž, b + aale «один» + «два»liší se od «два» + «один»jazyků, kde je operátor +přetížen zřetězením řetězců .
  • Přetížení operátorů činí fragmenty programu citlivějšími na kontext. Bez znalosti typů operandů zahrnutých ve výrazu je nemožné pochopit, co výraz dělá, pokud používá přetížené operátory. Například v programu C++ může operátor <<znamenat jak bitový posun, výstup do proudu, tak posun znaků v řetězci o daný počet pozic. Výraz a << 1vrátí:
    • výsledek bitového posunutí hodnoty ao jeden bit doleva if aje celé číslo;
    • if a - řetězec, pak výsledkem bude řetězec s jednou mezerou přidanou na konec (znak po znaku se posune o 1 pozici doleva) a v různých počítačových systémech kód mezery může se lišit;
    • ale pokud aje výstupní proud , stejný výraz vypíše do tohoto proudu číslo 1 «1».

Tento problém přirozeně vyplývá z předchozích dvou. Snadno se vyrovná přijetím dohod a obecnou kulturou programování.

Klasifikace

Níže je uvedena klasifikace některých programovacích jazyků podle toho, zda umožňují přetížení operátorů a zda jsou operátoři omezeni na předdefinovanou sadu:

Mnoho
operátorů

Žádné přetížení

Dochází k přetížení
Pouze
předdefinované

C
Java
JavaScript
Objective-C
Pascal
PHP
ActionScript
Go

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

Báječný

Je možné
zavést nové

ML
Pico
Lisp

Algol 68
Fortran
Haskell
PostgreSQL
Prolog
Perl 6
Seed7
Smalltalk
Julia

Poznámky

  1. Herbert Schildt. Kompletní průvodce C# 4.0, 2011.

Viz také