Erős típusok 2.

Dobra Gábor, Czirkos Zoltán · 2026.11.01.

Erős típusok 2.

A laborokhoz

A laborok mellé minden héten lesz kiírva egy beadandó az admin portálon. Ide óra végén töltsd fel a forráskódokat (*.cpp, *.h)! A feladatokat ezért külön projektben oldd majd meg, ne írd felül a megoldásokat.

Labor otthoni munkában

A labor teljesítéséhez legalább az első két feladatot meg kell oldani.

  1. Rejtett komplexitás: a megoldásokat kommentben írd a kód mellé, magyarázatokkal.
  2. Kivételek a veremben: a kódot ne add be teszteletlenül! Figyelj arra, hogy a sablon kódnak csak a használt része példányosodik; hívd meg a tesztben mindegyik függvényt! Előbb dolgozatsz egész számokkal, de később próbáld ki a vermet úgy is, hogy „igazi” munkát is végez a beszúrás és a törlés; konstruktorral, destruktorral rendelkező osztállyal példányosítsd a vermet! Lehet ez std::string, vagy az extrák alatt elérhető SuperNoisy.
  3. Bináris fa: tesztelj is, mint az előzőnél!

1. Polimorf típus

Tudjuk, hogy heterogén kollekcióhoz szükségünk van egy indirekcióra az objektumok eltérő típusa és mérete miatt. Ez kényelmetlen, mert a plusz indirekció és az ismeretlen típus is nehézkessé teszi az objektumok érték szerinti kezelését.

A másolás megoldható egy clone(), amit virtuális konstruktorként emlegetünk, még ha valójában nem is konstruktor. Az érték szerint kezeléshez pedig az előadáson készítettünk egy segédosztályt, amely másoláskor az objektum clone() függvényét hívja. Ezzel persze egy új elvárásunk van a tartalmazni kívánt típus felé: legyen clone() függvénye:

class Shape {
  public:
    virtual Shape * clone() const = 0;
    /* ... */
};
class Triangle: public Shape { /* ... */ };
class Rectangle: public Shape { /* ... */ };

PolymorphicValue<Shape> s1(new Rectangle);
PolymorphicValue<Shape> s2 = s1;        /* p1.ptr_->clone() */

Erre azért van szükség, mert a PolymorphicValue osztály konstruktora Shape * paramétert kap, és nem ismeri a lemásolandó objektum típusát. Az intruzív megvalósítás azonban nem előnyös: A PolymorphicValue feladata, a memóriakezelés egy része bekerült a Shape osztályba és annak leszármazottaiba.

Viszont ha más megközelítéssel indulnánk neki a feladat megoldásának, ismerhetné a típust. Ha sablon lenne a PolymorphicValue konstruktora, akkor átvehetné érték szerint is az objektumot, amelyet tartalmazni kell, és ismert lenne a típusa is:

PolymorphicValueV2<Shape> s1( Rectangle{} );    /* template-es ctor */
PolymorphicValueV2<Shape> s2( Triangle{} );

PolymorphicValueV2<Shape> s3 = s1;          /* Rectangle copy ctor, Triangle copy ctor */
PolymorphicValueV2<Shape> s4 = s1;

s3->get_area();                             /* Shape* és Shape& */
(*s3).get_area();

Oldd meg, hogy ez így működjön! Ehhez szükséged lesz egy type erasure-re is, amiről előadáson esett szó. Ha mindent jól csinálsz, a clone() függvény törölhető lesz; a PolymorphicValueV2 osztályod közvetlenül a tartalmazott objektum másoló konstruktorát fogja használni, és semmilyen egyéb függvényt nem vár annak interfészén.

Ötletek

  • A type erasure-höz kelleni fog egy rejtett osztályhierarchia: ősosztály, template-es leszármazott. A tárolt objektumnak a leszármazottban a helye.
  • Kezdd a template-es konstruktorral! Abből egyértelmű lesz, pontosan mit kell tárolni, és mit hogyan lehet elérni.
  • A memóriakezelést leegyszerűsíti, ha az osztályod nem nyers pointert, hanem std::unique_ptr-t tárol. Így a template-es konstruktoron kívül csak a másolással kell foglalkoznod.

Részletek

  • Legyen az osztályodnak másoló és mozgató template-es konstruktora is!
  • A tárolt objektumot az operator* és operator-> segítségével lehet elérni.
  • Legyen explicit bool konverziós operátora, hogy lehessen ellenőrizni, üres-e.
  • Extra: a tárolt objektum elérését érdemes azzal gyorsítani, hogy a címét közvetlenül a PolymorphicValueV2 tárolja.
Megoldás

template<typename Base>
class PolymorphicValueV2 {
    struct PolymorphicBase {
        virtual PolymorphicBase* clone() const = 0;
        virtual Base* obj() = 0;
        virtual ~PolymorphicBase() = default;
    };

    template<typename Derived>
    struct PolymorphicHolder : public PolymorphicBase {
        Derived obj_;

        PolymorphicHolder(Derived const& d) : obj_{d} {}
        PolymorphicHolder(Derived&& d) : obj_{std::move(d)} {}

        Base* obj() override {
            return &obj_;
        }
        PolymorphicBase* clone() const override {
            return new PolymorphicHolder{*this};
        }
    };

    std::unique_ptr<PolymorphicBase> base_;

public:

    PolymorphicValueV2() = default;

    template<typename Derived>
    explicit PolymorphicValueV2(Derived const& d)
        : base_{new PolymorphicHolder{d}}
    { }

    template<typename Derived>
    explicit PolymorphicValueV2(Derived&& d)
        : base_{new PolymorphicHolder{std::move(d)}} {
    }

    PolymorphicValueV2(PolymorphicValueV2&& other) noexcept = default;
    PolymorphicValueV2& operator=(PolymorphicValueV2&& other) noexcept = default;

    PolymorphicValueV2(PolymorphicValueV2 const& other)
        : base_{other.base_ ? other.base_->clone() : nullptr} {
    }

    PolymorphicValueV2& operator=(PolymorphicValueV2 const& other) {
        if (this != &other) {
            base_.reset(other.base_ ? other.base_->clone() : nullptr);
        }
        return *this;
    }

    explicit operator bool() const { return base_ != nullptr; }

    Base& operator*() { return *base_->obj(); }
    Base const& operator*() const { return *base_->obj(); }

    Base* operator->() { return base_->obj(); }
    Base const* operator->() const { return base_->obj(); }
};