Wektor klas pochodnych – błąd kompilacji

0

Mam problem z wektorem klas pochodnych. Otóż pokazuje mi błąd :|

error: 'class Figura' has no member named 'r'

Z tego co czytałem powinienem tu użyć dynamic_cast, ale nie wiem jak dokładnie go tutaj użyć. Ktoś może mi pomóc? Z góry dziękuje.

#include <iostream>
#include <vector>
using namespace std;
class Figura {
public:
    virtual void metoda(){};
};

class Kolo : public Figura {
public:
    int r;
    virtual void metoda(){};
};

int main()
{
    vector<Figura*> pojemnik;
    Kolo k;
    Figura* f;
    f = &k;
    pojemnik.push_back(f);
    pojemnik[0]->r = 2;

    return 0;
}
1

Masz tu chyba problem XY, co chcesz osiągnąć? Zastanów się co będzie, jak będziesz miał tam Kwadrat, który r nie ma.

0

Chcę mieć po prostu jakiś kontener na elementy typu koła, kwadraty itp. Znam tylko vector i chciałem go użyć, a co do r kwadratu , to po prostu bym go nie używał. Chyba, że masz jakiś inny pomysł na kontener ?

2

Generalnie jak chcesz mieć kontener klas bazowych, to używaj klas bazowych. https://pl.wikipedia.org/wiki/Zasada_podstawienia_Liskov

0

Być może źle Cie zrozumiałem, ale ja nie chcę kontenera klas bazowych, tylko jakiś jeden kontener w, którym mógłbym przechowywać w jakiś sposób obiekty wszystkich klas pochodnych. Szukając weny w internecie trafiłem na vector <Figura *> pojemnik, ale jak widać nie wiem jak się nim posługiwać.

0

Coś takiego zadziała, nie wiem natomiast jak dobrą praktyką to jest, może @kq zaproponuje coś lepszego?

vector<Figura*> pojemnik;

// ...
Kolo* k = dynamic_cast<Kolo*>(pojemnik[0]);
if (k != nullptr) {
    std::cout << k->r;
}
1

Sprawdzanie typu przez dynamic_cast nie jest zupełnie dobrą praktyką (choć jest czasem przydatne). Jak chcesz klasy bazowe to masz klasy bazowe i korzystasz ze wspólnych interfejsów.

Ewentualnie variant, ale ponawiam stwierdzenie, że tutaj jest XY, bo wektor klas bazowych to jedno, a co chcesz z nim zrobić to drugie.

0

Tworząc vektor klasy Figura masz dostęp do jej metod i pól, za to nowe metody dodane w klasie pochodnej nie są widoczne, w Twoim wypadku jest to tylko i wyłącznie metoda metoda() ,
dynamic_cast pomaga rzutować na inny typ jeśli jest to możliwe ale w zamian jest to czasochłonny proces
np: każdy kwadrat jest figurą ale za to nie każda figura jest kwadratem
bez dynamic_cast możesz zrobić tylko takie coś z obiektami w vektorze

pojemnik->metoda() 
0

W klasie Figura dodaj wirtualną funkcję skalującą i zaimplementuj ją odpowiednio w klasach pochodnych.

0

Możesz też skorzystać z wizytatora, oczywiście jest to overkill i używanie jest upierdliwe, ale skoro nie podałeś problemu, który próbujesz rozwiązać może Ci się akurat przydać:

#include <iostream>
#include <vector>

using namespace std;

class Figura;
class Kolo;

struct WizytatorBaza
{
	virtual void Wizytuj(Figura*) const {}
	virtual void Wizytuj(Kolo*) const {}
};

class Figura {
public:
    virtual void metoda() {}
    virtual void Akceptuj(const WizytatorBaza& w) { w.Wizytuj(this); }
};
 
class Kolo : public Figura {
public:
    int r;
    virtual void metoda() override {}
    virtual void Akceptuj(const WizytatorBaza& w) { w.Wizytuj(this); }
};

struct PrzykladowyWizytator : WizytatorBaza
{
	void Wizytuj(Kolo* k) const override 
	{ 
		k->r = 2; // możesz też pchnąć przez ctor co chcesz przypisać
		cout << "Przypisalem!\r\n";
	}
};

int main() {
	vector<Figura*> pojemnik;
    Kolo k;
    Figura* f;
    f = &k;
    pojemnik.push_back(f);
    pojemnik[0]->Akceptuj(PrzykladowyWizytator());
    
	return 0;
}
0

dynamic_cast wskazuje prawie zawsze -- moim skromnym zdaniem -- na błąd projektowy. Jeśli już uczysz się obiektów, to zacznij od metod abstrakcyjnych (plus ewentualne własne wyjątki) i osiągniesz tym to, co chcesz. Tu masz Twój przykład trochę zmieniony i rozszerzony tak, by działał w miarę sensownie bez dynamic_cast.

#include <iostream>
#include <vector>

using namespace std;

class Wyjatek_NieIstniejePoleRWTejKlasie {};

class Figura {
public:
    virtual void metoda(){};
    virtual void ustaw_r(int r_) =0;
};

class Kolo : public Figura {
public:
    int r;
    virtual void metoda(){};
    virtual void ustaw_r(int r_) { r = r_; cout << "Ustawiono r.\n"; };
};

class Kwadrat : public Figura {
public:
    int r;
    virtual void metoda(){};
    virtual void ustaw_r(int r_) { throw Wyjatek_NieIstniejePoleRWTejKlasie(); };
};

int main()
{
    vector<Figura*> pojemnik;
    Kolo kolo;
    Kwadrat kwadrat;
    pojemnik.push_back(&kolo);
    pojemnik.push_back(&kwadrat);
    pojemnik[0]->ustaw_r(2);
    pojemnik[1]->ustaw_r(3);

    return 0;
}
1

Sądzę, że "zaśmiecanie" interfejsu jak to uczyniłeś w swojej propozycji jest większym błędem projektowym niż użycie dynamic_cast.

0
ketam napisał(a):

Sądzę, że "zaśmiecanie" interfejsu jak to uczyniłeś w swojej propozycji jest większym błędem projektowym niż użycie dynamic_cast.

Sądzę, że nie. :) O ile to do mnie było...

A teraz może jakieś merytoryczne uzasadnienia? :)

PS. Ale żeby było jasne -- nie uważam mojego rozwiązania za jakieś świetnie -- chodziło o dostosowanie tego co OP chciał zrobić do działającego C++...

0

Tak, to było do ciebie :) i pomimo, że ty nie podałeś merytorycznego uzasadnienia dlaczego dynamic_cast według ciebie świadczy "prawie zawsze o błędzie projektowym" ja postaram się wytłumaczyć jak to wygląda z mojej perspektywy.

Podejście, które zaproponowałeś jest złe w ogólności. Nie widać tego niestety w tak prostym kodzie jednak prowadzi ono przy bardziej złożonych hierarchiach do naruszenia zasady pojedynczej odpowiedzialności. Klasa bazowa zaczyna być wypełniana przez wiele niezwiązanych z nią metod. Interejs, który udostępnia przestaje być spójny z pierwotnymi założeniami i bytem, który modelowała. Ponadto zaczyna ona posiadać wiedzę na temat swoich klas pochodnych (co już twój przykład pokazuje) co stanowi poważne naruszenie dobrych praktyk projektowych.

W twoim przypadku dodajesz do interfejsu metodę ustawiającą promień, ale co to znaczy dla prostokątów, trójkątów itd. ? Ponadto już na poziomie klasy bazowej zakładasz, że klasy pochodne będą posiadały promień - "niektóre" jak się później okazuje. Czyli uzależniasz interfejs wszystkich figur od jakiegoś podzbioru klas bazowych. Jak będziesz chciał dodać informację o wysokości, ilości równoległych boków, przękątnych itd. to za każdym razem będziesz rozszerzał klase bazową?

To jest próba obejścia błędu projektowego popełniając jeszcze większy błąd, który będzie się mścił wraz z rozrostem hierarchii.

Inna kwestią jest fakt, że problem, który został tutaj poruszony z założenia nie powinien wystąpić ponieważ jeśli autor chce wykonać operacje specyficzną dla okręgu to nie powinien do tego używać kontenera wszystkich figur, a co najwyżej kontenera samych okręgów i figur z nich się wywodzących.

Jeśli trzymasz kontener homogeniczny jakiś obiektów to naturalne jest wykonywanie operacji udostępnionych przez interfejs tychże obiektów.

Podejście, które ja zaproponowałem też "paskudzi" interfejs, ale jest to w zasadzie jedna metoda i ich ilość nie powinna się już więcej zmienić. Tutaj kolejne klasy pochodne są obsłużone przez tą klase wizytora (co też w zasadzie powoduje, że interfejs wizytora puchnie ale umówmy się - jest to zgodne z jej przeznaczeniem).

Gdyby twoje rozwiązanie było dobre to w bibliotekach takich jak Qt częsciej spotykałbyś rozepchane interfejsy do granic absurdu niż używał dynamic_casta.

Teraz czekam na merytoryczną odpowiedź dlaczego dynamic_cast świadczy prawie zawsze o błędzie projektowym :) Nie mówię, że tak nie jest tylko chce wiedzieć, że rozmawiam z kimś kto myśli samodzielnie i ma własne przemyślenia na ten temat, a nie powtarza ślepo bez zrozumienia co mówią inni.

1 użytkowników online, w tym zalogowanych: 0, gości: 1