Cvičení 09

Úvod do cvičení

Na tomto cvičení budete pracovat s operátory, vyzkoušíte si implementaci operátorů jako metod i jako samostatných funkcí. Naučíte se vytvořit kopii objektu, jehož typ není v dobře překladu známý. Na tomto cvičení využijete upravené třídy ze druhé domácí úlohy, které si můžete stáhnout zde.

Kromě jednoduchých tříd Circle a Rectangle přibude ještě jeden potomek třídy Geometry2D a to třída ObjectSet. Ta představuje množinu jednoduchých objektů, které jsou uložené v privátním atributu objects typu std::vector. Pomocí operátorů bude možné provádět základní množinové operace – průnik, sjednocení a rozdíl. Prozatím pro toto cvičení využijeme pro ukládání prvků množiny třídu std::vector, která sice není ideální, ale práce s ní je jednodušší.

Krátké povídání o operátorech v C++

Metoda či funkce

Obecně lze říct, že operátory lze implementovat buď jako samostatné funkce, nebo jako členské metody (ze zjevných důvodů ovšem nikdy současně). Kdy tedy implementovat operátor jako členskou metodu a kdy jako samostatnou funkci?

Pro zjednodušení se lze řídit těmito doporučeními:

  • Několik specifických operátorů (operator[], operator=, operator() a operator→) lze implementovat jenom jako členské metody.
  • Pokud je to unární operátor, nechť je implementován jako členská metoda.
  • Pokud je to binární operátor, který nemění své operandy, nechť je implementován jako samostatná funkce.
  • Ovšem pokud se binární operátor nechová k oboum svým operandům totožně, například tím, že mění stav levého operandu, nechť je implementován jako metoda.

Většinou je operátor malá funkce, kterou bychom nejraději napsali současně s definicí do hlavičkového souboru. Protože by ale docházelo k problémům při překladu (linker by si neporadil s vícenásobnou definicí funkce – operátoru), je třeba definici operátoru uvést klíčovým slovem inline:

SomeObject.h
...
inline bool operator==(const SomeObject &lhs, const SomeObject &rhs) {
    // testování shody
}

Operátory porovnání

Při implementaci operátorů porovnání je vhodné si uvědomit, že existuje celkem 6 relačních operátorů, které lze rozdělit na dvě skupiny:

  • relace ekvivalence: operator== a operator!=
  • relace uspořádání: operator<, operator⇐, operator> a operator>=

Lze celkem s jistotou prohlásit, že pokud umožníme zápis a < b, bude někdy nějaký programátor chtít zapsat b > a. Proto pokud implementujete alespoň jeden operátor ze skupiny, je velmi vhodné implementovat všechny. Abychom ale zamezili duplikacím kódu, je vhodné implementovat relační operátory nějak takto:

SomeObject.h
inline bool operator==(const SomeObject &lhs, const SomeObject &rhs) { /* porovnávání */ }
inline bool operator!=(const SomeObject &lhs, const SomeObject &rhs) { return !(lhs == rhs); }
inline bool operator< (const SomeObject &lhs, const SomeObject &rhs) { /* porovnání */ }
inline bool operator<=(const SomeObject &lhs, const SomeObject &rhs) { return !(lhs > rhs); }
inline bool operator> (const SomeObject &lhs, const SomeObject &rhs) { return rhs < lhs; }
inline bool operator>=(const SomeObject &lhs, const SomeObject &rhs) { return !(lhs < rhs); }

Příjemné používání přetížených operátorů

Při přetěžování je vhodné se navíc řídit následujícími radami, aby bylo používání přetížených operátorů snadné, bezproblémové a intuitivní.

  • Pokud přetěžujete operátory, neměňte jejich sémantiku. + by mělo věci sčítat, - zase odečítat.
  • Pokud umožníte a++, umožněte taktéž ++a.
  • Pokud sémantika binárního operátoru umožňuje komutativitu (=nezávislost pořadí operandů), měli byste ji taktéž zohlednit.
  • Pokud implementujete operátory sčítání, násobení a další, implementujte taktéž i jejich zkrácenou variantu:
NumericOperators.h
class A {
    int number;
public:
    A &operator+=(const A &other) {
        number += other.number;
        return *this;
    }
};
inline A operator+(A lhs, const A &rhs) {
    return lhs += rhs;
}
Povšimněte si rozdílných typů parametrů funkce operator+. První se bere hodnotou, protože si tak rovnou vytvoříme kopii a následně můžeme využít již implementovaného operator+=.
Pro více informací si můžete přečíst například tento článek.

První úkol

Operace sjednocení umožní sjednotit dvě množiny, případně přidat prvek do množiny. Tato operace je komutativní, to znamená, že nezáleží na pořadí operandů.

Zadání úkolu

Implementujte operator+ a operator+=.

  • operator+ nesmí měnit stavy svých operandů
  • operator+= mění pouze svůj levý operand
  • Operátory napište tak, abyste
    • neduplikovali kód
    • používali správné typy parametrů funkcí/metod
    • měli správný návratový typ funkcí/metod
    • správně určili, který operátor má být funkcí a který metodou
    • podporovali komutativitu v případě, že jeden operand je množina a druhý je prvek množiny
  • Dejte si pozor, abyste nezpůsobovali memory leaky.
std::find_if, ObjectSet::Compare

Druhý úkol

Implementujte kopírovací konstruktor, přiřazovací operátor a metodu clone.

Zadání úkolu

Cílem je implementovat kopírovací konstruktor, přiřazovací operátor a metodu swap dle návodu, který byl prezentován na přednášce.

Kopírovací konstruktor implementujte tak, aby se vytvořila „hluboká“ kopie – musíte vytvořit kopie všech prvků z původní množiny. Pro správné zkopírování všech objektů implementujte do všech potomků Geometry2D virtuální metodu clone, jejíž hlavičku najdete v souboru Geometry2D.h.

Použijte copy & swap idiom:
  1. Implementace kopírovacího konstruktoru
  2. Implementace metody swap
    • specializace funkce std::swap je již poskytnuta
  3. Implementace přiřazovacího operátoru, který bere parametr hodnotou a volá metodu swap

Třetí úkol

Operace rozdílu umožní odečíst od sebe dvě množiny, případně odebrat prvek z množiny.

Zadání úkolu

Implementujte operator- a operator-=.

  • operator- nesmí měnit stavy svých operandů
  • operator-= mění pouze svůj levý operand
  • Operátory napište tak, abyste
    • neduplikovali kód
    • používali správné typy parametrů funkcí/metod
    • měli správný návratový typ funkcí/metod
    • správně určili, který operátor má být funkcí a který metodou
  • Dejte si pozor, abyste nezpůsobovali memory leaky.
std::find_if, ObjectSet::Compare

Čtvrtý úkol

Operace průniku umožní vybrat ty prvky z množin, které patří do obou množin.

Zadání úkolu

Implementujte operator& a operator&=.

  • operator& nesmí měnit stavy svých operandů
  • operator&= mění pouze svůj levý operand
  • Operátory napište tak, abyste
    • neduplikovali kód
    • používali správné typy parametrů funkcí/metod
    • měli správný návratový typ funkcí/metod
    • správně určili, který operátor má být funkcí a který metodou
  • Dejte si pozor, abyste nezpůsobovali memory leaky.
std::find_if, ObjectSet::Compare

Vzorové řešení

QR Code
QR Code public:pb161:fall14_cviko09 (generated for current page)