Dědičnost prvků v std::list rubrika: Programování: C/C++

2 Andreaw Fean
položil/-a 27.12.2021

Prosím o radu:

Mám takovouto dědičnost, kód:

#include <iostream>
#include <list>

 
using namespace std;
 
 
struct Element
{
    virtual ~Element() {}
};
 
 
struct Control : public Element
{
    string name;
    string value;
};
 
 
struct Container : public Element
{
    string name;
    list<Element> items;
};
 
 
int main()
{
    Element* a = new Element;
    Element* b = new Control;
 
    Control c = dynamic_cast<Control&>(*b);
 
    if (typeid(*b) == typeid(Element)) {
        cout << "Typ identifikovan v dobe prekladu." << endl;
    }
    else {
        cout << "Typ identifikovan pri behu programu." << endl;
    }
    cout << "val a: " << typeid(a).name() 
        << " -> " << typeid(*a).name() << endl;
    cout << "val b: " << typeid(b).name()
        << " -> " << typeid(*b).name() << endl;
    cout << "val c: " << typeid(c).name() << endl;
 
    list<Element> xs;
    // xs.push_back(*a);
    xs.push_back(*b);
 
    for (list<Element>::iterator it = xs.begin(); it != xs.end(); ++it) {
        cout << "val xs[]: " << typeid(it).name()
            << " -> " << typeid(*it).name() << endl;
        Control x = dynamic_cast<Control&>(*it);
        cout << "val xs[], x: " << typeid(x).name() << endl;
    }
 
    return 0;
}

Padá mi to při pokusu přetypovat hodnotu iterátoru na požadovaný typ.

Jak se to prosímvás dělá správně?

Cílem je, abych mohl mít v komponentě prvky i další komponenty.

Díky.

odkaz
5 David Macek
odpověděl/-a 27.12.2021

Jednoduchá odpověď: list<Element*> (případně místo obyčejného ukazatele použít některou z šikovnějších obálek z https://en.cppreference.com/w/cpp/memory)

Složitější odpověď:

Kód sice na začátku tvoří objekty Element a Control na haldě, ale pak s nimi tak nepracuje. Při výrobě c dochází k tvorbě nového objektu, taktéž při vkládání do xs, ani x by neuložilo odkaz na objekt ze seznamu, nýbrž na nový objekt. Lze to pěkně vidět, když se na třídě Element zakážou nebo přeimplementují automatické metody (https://www.enyo.de/fw/notes/cpp-auto-members.html):

struct Element
{
    virtual ~Element() {}
    Element() {}
    Element(const Element& that) { cout << "copy constructor" << endl; }
    Element operator=(const Element& that) { cout << "copy assignment" << endl; return *this; }
};
// nebo
struct Element
{
    virtual ~Element() {}
    Element() {}
    Element(const Element& that) = delete;
    Element operator=(const Element& that) = delete;
};

U c stačí změnit typ na Control& c, se seznamem je to ale horší. Teoreticky by mělo být možné v něm mít také reference, ale ani s std::reference_wrapper se mi nepodařilo rozjet to přetypování. Každopádně reference by možná něco ovlivnily, ale pro běžné využití v programech by stejně byly nedostatečné, většinou je totiž potřeba, aby prvky v podobných seznamech přežily vyvolání funkcí, ve kterých byly vytvořeny, takže to budou muset být ukazatele. Po změně seznamu na list<Element*> xs a příslušných opravách dále již přetypování funguje.

Jinak pokud to lze, doporučuji využívat polymorfismus, tedy všechny operace, které se liší podle typu objektu, implementovat jako virtuální metody na těch třídách. Potom není potřeba řešit přetypování.

Pro plný přístup na Devel.cz se prosím přihlaste:

Rychlé přihlášení přes sociální sítě:

Nebo se přihlaste jménem a heslem:

Zadejte prosím svou e-mailovou adresu.
Zadejte své heslo.