Klasy abstrakcyjne

0

Mam do napisania pewien dość obszerny projekt i chciałbym go zrobić na klasie abstrakcyjnej jednakże pojawił sie pewien problem którego do końca nie potrafię rozwiązać mianowicie:
Załóżmy że mamy hierarchie dziedziczenia jak poniżej

class Ojciec 
{
    void virtual napisz() = 0;
};

class Syn: public Ojciec
{
    void napisz() 
      {
          printf("jestem synem\n");
      }
    void opisz() 
      {
          printf("class Syn\n");
      }
};

class Corka : public Ojciec
{
    void napisz() 
      {
          printf("jestem corka\n");
      }
    void wypisz() 
      {
          printf("class Corka\n");
      }
};

int main ()
{
    Ojciec *tab[2];
    tab[0] = new Syn;
    tab[1] = new Corka;
/*     tab[0] -> opisz();
        tab[1] -> wypisz();    */
return 0;
}

i teraz wiadomo to się nie skompiluje a moje pytanie jest czy istnieje sposób żeby to się skompilowało i działało poprawnie a jak tak to jaki. Sam wymyśliłem żeby do klasy abstrakcyjnej "władować" wszystkie metody z klas które po niej dziedziczą a potem ograniczać przy dziedziczeniu dostęp do metod (ale nie jestem pewien czy kompilator nie wyrzuci mi stosu błędów że nie zdeklarowałem metod zdeklarowanych jako virtualne z dziedziczenia). Normalnie zrobił bym obiekty class Syn i Corka lecz w projekcie drzewo dziedziczenia ma głębokość 5 poziomów i liczy 27 klas pochodnych. Z góry wielkie dzięki za pomoc.

0

czy istnieje sposób żeby to się skompilowało i działało poprawnie

Tak , powtórzyć podstawy obiektowego C++ i obudzić się , robisz zwykłe 'literówki' i pokręciłeś nazwy funkcji .

#include <stdio.h>
#include <iostream.h>

class Ojciec
{
   public:
    void virtual napisz() = 0;
    void virtual wypisz() = 0;
    virtual ~Ojciec(){};        // jeśli obiekty pochodne tej klasy będą operować
                                        // na pamięci przydzielanej dynamicznie
                                        // dla swoich składników, destruktor
                                        // wirtualny pozwala na prawidłowe
                                        // zwolnienie pamięci
                                        
};

class Syn: public Ojciec
{
  public:
    void napisz()
      {
          printf("jestem synem\n");
      }
    void wypisz()
      {
          printf("class Syn\n");
      }
      ~Syn(){};
};

class Corka : public Ojciec
{
   public:
    void napisz()
      {
          printf("jestem corka\n");
      }
    void wypisz()
      {
          printf("class Corka\n");
      }
      ~Corka(){};
};

int main ()
{
    Ojciec *tab[2];
    tab[0] = new Syn;
    tab[1] = new Corka;
     tab[0] -> napisz();
     tab[1] -> wypisz();

        cin.get();

        delete tab[0];
        delete tab[1];
return 0;
}
0

Dziedziczenie w życiu codziennym i w programowaniu to zupełnie inne rzeczy. W życiu codziennym córka z reguły dziedziczy po ojcu, trudno zatomiast znaleźć powody, dla których klasa Corka powinna dziedziczyć z klasy Ojciec.

0
bogdans napisał(a)

trudno zatomiast znaleźć powody, dla których klasa Corka powinna dziedziczyć z klasy Ojciec.

E tam, po wywołaniu destruktora na obiekcie Ojciec powinna zostać wywołana metoda Notariusz.RealizujTestament(), dzięki której właściwość Ojciec.Majątek zostanie rozdzielona na kolekcję Ojciec.Dzieci wg reguł zapisanych w Notariusz.Testament ;P

0

@bogdans bez przesady, wszystko zalezy od tego jakie informacje są w klasie ojciec przechowywane.
Bo jeśli to będzie np. nazwisko to córka jak najbardziej moze je dziedziczyć :P

0

somekind, co Ty, z notariusza chcesz bridge'a robić?

0

@somekind przeprowadził ciekawą analizę

E tam, po wywołaniu destruktora na obiekcie Ojciec powinna zostać wywołana metoda Notariusz.RealizujTestament(), dzięki której właściwość Ojciec.Majątek zostanie rozdzielona na kolekcję Ojciec.Dzieci wg reguł zapisanych w Notariusz.Testament

A jaki to ma związek z tym że klasa Corka dziedziczy (bądź nie) z klasy Ojciec?
@Shalom bez przesady, posiadanie przez dwie klasy jednego wspólnego pola nie jest wystarczającym powodem by jedna z tych klas dziedziczyła po drugiej.

0

A wy tu o pierdołach, a może gość nie zrobił literówki, tylko umyślał sobie naprawdę taką a nie inną strukturę. Nota bene - wiem, że zrąbaną, ale proszę nie mówić, żeby robić inną - bo 2 bardzo popularne, komercyjne biblioteki takiej hierarchii używają.

#include <iostream>
#include <stdexcept>
using namespace std;

class Root {
    protected:
        /// no dobra - czyste wirtualne nie są używane
        /// brzydko, ale tak już jest ;]
        virtual void a() { throw runtime_error("not implemented"); }
        virtual void b() { throw runtime_error("not implemented"); }
        virtual void c() { throw runtime_error("not implemented"); }
    };

struct A : public Root {
    public:
        void a() { cout << "a" << endl; }
    };

struct B : public Root {
    public:
        void b() { cout << "b" << endl; }
    };

struct C : public Root {
    public:
        void c() { cout << "c" << endl; }
    };

/// pomocnicza
template<class A, class B> A* cast(B* value) {
    return &dynamic_cast<A&>(*value);
    }

int main() {
    A x;
    B y;
    C z;

    /// obiekty właściwe: część metod chroniona
    x.a(); /* x.b(); x.c(); */
    y.b(); /* y.a(); y.c(); */
    z.c(); /* z.a(); z.b(); */

    Root* t[3] = { &x, &y, &z };

    /// obiekt bazowy: wszystko chronione
    /* t[0]->a();  t[0]->b(); t[0]->c(); */
    /* t[1]->a();  t[1]->b(); t[1]->c(); */
    /* t[2]->a();  t[2]->b(); t[2]->c(); */


    try {
        /// rzutowanie na obiekty właściwe
        cast<A>(t[0]) -> a();
        cast<B>(t[1]) -> b();
        cast<C>(t[2]) -> c();
        }
    catch(exception& error) {
        cerr << error.what() << endl;
        }

    return 0;
    }

</b>
0
bogdans napisał(a)

A jaki to ma związek z tym że klasa Corka dziedziczy (bądź nie) z klasy Ojciec?

Chciałem tylko w żartobliwy sposób zwrócić uwagę na tą subtelną różnicę semantyczną między dziedziczeniem w programowaniu a w świecie rzeczywistym ;)

0

Wielkie dzięki wszystkim za zainteresowanie i odpowiedzi ale nikt nie odpowiedział na moje pytanie

dzejo napisał(a)

Tak , powtórzyć podstawy obiektowego C++ i obudzić się , robisz zwykłe 'literówki' i pokręciłeś nazwy funkcji .

to nie są literówki choć przyznaje że czytelniej by było gdybym nazwał funkcje całkowicie odmienne. Tak więc ponawiam moje pytanie: czy istnieje sposób by przy dokładniej takiej hierarchii dziedziczenia to zadziałało poprawnie.

0

no ja przynajmniej mam uczciwe poczucie, że i owszem, odpowiedziałem. Z tym, że nie podałem explicite rzeczy oczywistej:

Nie, nie istnieje sposób, żeby to dokładnie tak działało, jak sobie umyślałeś:

t[0] -> a(); // działa, bo wskazuje na A
t[1] -> b(); // działa, bo wskazuje na B
t[2] -> c(); // działa, bo wskazuje na C

Musisz jawnie rzutować:

cast<A>(t[0]) -> a();
cast<B>(t[1]) -> b();
cast<C>(t[2]) -> c();

Oraz:
dawanie w interfejsie pure virtual jest w takiej hierarchii skrajnie niebezpieczne. Bo jak ktoś zrzutuje wskaźnik inaczej, niż dynamic_castem - to w runtime zamiast wyjątku (jak u mnie) SEGFAULT poleci...

Końcowo:
Pokazałem jak twoją wizję robi się w "C++way", żeby było w miarę bezpiecznie. Nie jest dokładnie tak jak chcesz, bo C++ (jakby powiedzieli niektórzy) to nie jest język w pełni wspierający OO. Możesz trochę to od d**y strony ostatecznie zrobić. Może wygodniejsze - ale o wyjątek czasu wykonania dużo dużo łatwiej w takim układzie.

(dopisane)

#include <iostream>
#include <stdexcept>
using namespace std;

class Root {
    public:
        /// czyste wirtualne nie mogą być, bo obiekt przecież nic nie potrafi
        /// a wszystko na nim można wywołać - horror, ale niech będzie
        virtual void a() { throw runtime_error("not implemented"); }
        virtual void b() { throw runtime_error("not implemented"); }
        virtual void c() { throw runtime_error("not implemented"); }
    };

struct A : public Root {
    protected:
        /// ukryjmy te nieużywalne
        using Root::b;
        using Root::c;
    public:
        void a() { cout << "a" << endl; }
    };

struct B : public Root {
    protected:
        using Root::a;
        using Root::c;
    public:
        void b() { cout << "b" << endl; }
    };

struct C : public Root {
    protected:
        using Root::a;
        using Root::b;
    public:
        void c() { cout << "c" << endl; }
    };

/// pomocnicza
template<class A, class B> A* cast(B* value) {
    return &dynamic_cast<A&>(*value);
    }

int main() {
    A x;
    B y;
    C z;

    /// obiekty właściwe: część metod chroniona
    x.a(); /* x.b(); x.c(); */
    y.b(); /* y.a(); y.c(); */
    z.c(); /* z.a(); z.b(); */

    cout << endl;

    Root* t[3] = { &x, &y, &z };

    /// obiekt bazowy: wszystko dostępne, ale wyjątków poleci w runtime...
    #define CATCHED_RUN(X, Y, Z) \
        try { X; } catch(...) { cout << "error: " << #X << endl; } \
        try { Y; } catch(...) { cout << "error: " << #Y << endl; } \
        try { Z; } catch(...) { cout << "error: " << #Z << endl; }

    CATCHED_RUN ( t[0]->a(),  t[0]->b(), t[0]->c() )
    CATCHED_RUN ( t[1]->a(),  t[1]->b(), t[1]->c() )
    CATCHED_RUN ( t[2]->a(),  t[2]->b(), t[2]->c() )

    cout << endl;

    try {
        /// bez rzutowania na obiekty właściwe
        t[0] -> a();
        t[1] -> b();
        t[2] -> c();
        }
    catch(exception& error) {
        cerr << error.what() << endl;
        }

    return 0;
    }

</b>
0

Wielkie dzięki za pomoc i odpowiedz na zadane pytanie.
Jeśli ktoś kiedyś chciałby skorzystać z pomysłów tu podanych dodam że z innych źródeł dowiedziałem się iż problem ten można rozwiązać dzięki sprawdzaniu przez dynamic_cast czy udało się rzutowanie (jeśli nie to if-em omijamy dalsze polecenia)

0

zgadza się. masz nawet dwa dynamic_casty, i warto mieć na uwadze ich różne zachowanie:

Bazowa * pointer = ... ;
Bazowa & reference = ... ;

Klasa * a = dynamic_cast<Klasa *>(pointer);
// będzie NULL, w razie porażki

Klasa & b = dynamic_cast<Klasa &>(reference);
// poleci wyjątek, w razie porażki

osobiście lubię wersję drugą, dlatego w pierwszym przykładzie używałem w funkcji pomocniczej takiego wymuszenia:

template<class A, class B> A* cast(B* value) {
    return &dynamic_cast<A&>(*value);
    }

niby pointer-na-pointer rzutuję, ale po drodze używam dynamic_cast<referencja> - więc funkcja rzuca wyjątkiem, zamiast NULLa dawać.

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