Zależności cykliczne

0

Witam!

Mam problem z zależnościami cyklicznymi między klasami. Rozwiązanie prostego przypadku znalazłem, czyli jak mam np 2 klasy: A i B i one się wzajemnie używają. Wstawiam wzajemne deklaracje tych klas class A; i class B; na nagłówkach i jest ok. Ale co mam zrobić kiedy mam np 8 klas, które tworzą łańcuszek, czyli np klasa A includuje B, B includuje C, C includuje D..... a potem znowu klasa H includuje A. No i tutaj nie wiem co mam zrobić? Mam w każdej z nich zrobić przeddeklaracje pozostałych?
Bardzo proszę o pomoc bo stoję przed widmem przeprojektowania wielkiego kawałka kodu.

Pozdrawiam!

0

zero) przeczytaj: http://4programmers.net/Forum/642607#id642607 tam jest szablon naglowka, ktorego uzywanie wiekszosc problemow Ci załata

inne:

  1. po pierwsze, pamietaj, ze #include wkleja wskazany plik na żywca w miejsce/zamiast #include
    z tego powodu nalezy umiejetnie korzystac z forward-declarations oraz raczej nie uzywac #pragma once, chyba ze dokladnie wiesz co sie dzieje

  2. klasy musisz zaprojektowac tak, aby cykliczne zaleznosci nie wystepowaly pomiedzy konkretnymi typami danych. Jezeli klasa Słoń zawiera w sobie pole o typie Mrówka, to znaczy ze typ Mrówka nie ma prawa zawierac w sobie pola o typie Słoń. Jest to obostrzenie przechodnie. Jesli teraz inny typ Świat zawiera w sobie pole typu Słoń, to Mrówka nie ma prawa takze zawierać pola typu Świat

  3. Mrówka może natomiast śmiało zawierac pola typu Słoń, Świat&, Świat&, Słoń** itp. Roznica tkwi w "zawieraniu" versus "zawieraniu odniesienia do". Mrówka nie moze zawierac Slonia, ale moze doskonale orientowac sie w którym Słoniu sie znajduje i pamietać do niego odniesienie. W polaczeniu z punktem 1) czyli z forward-declarations mozesz osiagnac prawie dowolne skomplikowanie pomiedzy klasami. Jedyny wyjatek -- cykliczne wiazania pomiedzy inner class z roznych outer class -- do nich dostep i pisanie forward-declarations moze byc bardzo trudne, albo wrecz niemozliwe

Wyjasnienie czemu tak jest wymaga opisania jak i po co kompilator oblicza rozmiary obiektow, w jakiej kolejnosci parsuje jakie fragmenty plikow, oczywiscie zaleznie od kompilatora ktorego uzywasz etc.. ale sadze ze swoj problem juz bedziesz potrafil rozwiazac.

0

Dzięki za szybką odpowiedź!

Ale jeszcze mam pytania: Jaka jest różnica między "klasa Słoń zawiera w sobie pole o typie Mrówka", a "zawierac pola typu Słoń*, Świat&, Świat*&, Słoń**" ? Tzn konkretnie nie rozumiem tego pierwszego. W moich klasach wszystkie pola, które są jakimiś obiektami są właśnie wskaźnikami (np:Słoń* słoń, Mrówka* mrówka), ale i tak żeby w klasie która zawiera te pola muszę umieścić includa z plikiem nagłowkowym do tutaj: Slon.h i Mrowka.h. Chyba, że w nagłówkowych wstawiam tylko forward declarations, a w pliku cpp gdzie już faktycznie jest używana ta klasa wstawiam includa z np mrowka.h. Nawet mi sie to tak teraz skompilowało, ale nie wiem czy to dobre podejście.

Co do tego szablonu: Czyli jak rozumiem jednak muszę w każdym z tych plikó w łańcuszku dodać te deklaracje? Czyli jak potem dostawię np dziewiątą klasę do tego łańcuszka to w niej muszę dodać znowu deklaracje pozostałych 8, a w tych pozostałych 8 definicję dziewiątej klasy?

0

Mógłbyś mniej wiecej przedstawić nam tą swoją hierarchię klas, bo moze najzwyczajniej w świecie niepotrzebnie to zagmatwałeś?

0
class Mrowka
{
    Slon* on_mnie_zjadl;    // odniesienie do slonia
};

class Slon
{
    Mrowka zjedzona;      // zawieranie mrowki
};

Mrowka m; m.on_mnie_zjadl->costam
Slon s; s.zjedzona.costam

serio nie widzisz roznicy miedzy tymi dwoma przypadkami?

a co do #include, nie jest prawda, ze klasa "Mrowka" powyzej wymaga #include Slon.
jedyne co ta powyzsza definicja mroki wymaga, to uprzedniej deklaracji klasy Slon.
Definicja!=deklaracja. Napisalem Ci juz o forward declaration, prawda?
Aby w/w kod sie kompilowal, wystarczy:

class Slon;  // FORWARD DECLARATION. Podpowiadamy kompilatorowi ze "kiedys taka klasa bedzie"

class Mrowka
{
    Slon* on_mnie_zjadl;    // odniesienie do slonia
};

class Slon
{
    Mrowka zjedzona;      // zawieranie mrowki
};

//Mrowka m; m.on_mnie_zjadl->costam
//Slon s; s.zjedzona.costam

I po co wiec Mrowce #include calego slonia, ktore by powodowalo cykliczna zaleznosc? Mrowka NIE ZALEZY od definicji klasy slon. Definicja Mrowki ZALEŻĄŁABY od klasy Slon w takim przypadku:

class Mrowka
{
    Slon* on_mnie_zjadl;
    void gryz_slonia_w_jelita()  // metoda z cialem!
    {
        on_mnie_zjadl->costam     // uzycie slonia!
    }
};

class Slon
{
    Mrowka zjedzona;
};

poniewaz teraz definicja ta zawiera METODE ktorej CIALO uzywa WNETRZA klasy Slon. W takim wypadku wymagane
jest aby cala definicja klasy Slon byla juz-wczesniej znana. Deklaracja nie wystarczy. Co wtedy? Wtedy przypominasz sobie, ze C/C++ maja pliki .CPP i .HPP:

 // Plik.HPP
class Slon;

class Mrowka
{
    Slon* on_mnie_zjadl;
    void gryz_slonia_w_jelita();   // wycinasz cialo metody
};

class Slon
{
    Mrowka zjedzona;      // zawieranie mrowki
};

// PLIK.CPP
#include "Plik.hpp"

void Mrowka::gryz_slonia_w_jelita()
{
    on_mnie_zjadl->costam
}

co teraz sie dzieje? Otoz, w pliku .hpp zniknely uzycia Slonia z Mrowki. DEFINICJA Mrowka nadal uzywa jedynie ODNIESIENIA do Slonia, czyli Slon*, nie dotyka zawartosci Slonia. Dzieki temu definicja Mrowki zostaje w 100% zadowolona poprzez forward-declaration 'class-slon-srednik'. Plik .CPP zas zalacza sobie naglowek .hpp (ktory to naglowek zostal wlasnie naprawiony) i defacto, bedac na samym koncu wszytkiego - zna wszystko i moze uzywac i wnetrza slonia, i wnetrza mrowki.

Wybacz, nie wiem jak Ci to prosciej opisac skoro (chyba) nie zrozumiales szablonu..
Tamten szablon pokazywal dokladnie to o czym teraz pisze. Wystarczy ze swoje pliki .hpp przerobisz zgodnie z nim, i powinno dzialac
..oczywiscie, chyba ze masz 3 tony kodu cial metod w plikach .hpp -- wtedy NIE zadziala, bo nie moze.. ciala trzeba w takim przypadku rozdzielic, nawet jesli sa to template<>'y

0

Spoko! Dzięki!

Już wiem o co biega. :) Generalnie w swoim kodzie w plikach nagłówkowych mam tylko i wyłącznie deklaracje klas i metod, a w plikach cpp mam już implementacje. Problem brał się stąd, że wszystkie includy wrzucałem do plików nagłówkowych (bo mam wrażenie, że gdzieś przeczytałem, że tak się powinno robić w c++) , a plik cpp danej klasy includował tylko własny plik nagłówkowy. Teraz jak przerzuciłem includa do pliku cpp, czyli tam gdzie pisałeś jest faktycznie wykorzystana implementacja danej metody jest ok. A w pliku nagłówkowym jest tylko forward declaration. Na swoje usprawiedliwienie mogę tylko powiedzieć, że jestem programistą javy i takie problemy są dla mnie nowe:)
Ale korzystając z okazji, że i tak już wyszedłem na nieuka to zadam jeszcze jedno pytanie: Rozumiem jaka jest różnica kiedy tworze jakieś 2 obiekty w ciele metody: np Slon* slon = new Slon(); oraz Slon slon. Wiem, ze w drugim wypadku to tworzenie tylko na stosie, że obiekt sam sie zwolni bo wyjsciu z metody w przeciwienstwie do pierwszego przypadku. Ale co jeśli to są pola klasy? U mnie wszystkie pola do obiektów są wskaźnikami: Slon* slon, które tworze w odpowiednim miejscu i zwalniam w destruktorze klasy. A jeśli pole metody jest zadeklarowane Slon slon;? To co to właściwie jest?

Pozdrawiam!

0

Ano, jesli wczesniej pisales w Javie, to sie nie dziwie, ani sie nie dziwie ze teraz masz same wskazniki i pelno new/delete.

Mowiac duzymi hasłami, róznica jest w 'cyklu zycia'.

  • Jesli cos tworzysz poprzez (gc)new, to to tak jak w Javie po prostu sobie zaczyna istniec, i w przeciwienstwie do Javy istniec bedzie juz zawsze az wywolasz mu delete/[], chyba ze jest zarzadzane, wtedy moze GC go kiedys sprzatnie. New i gcnew tworza obiekty na stertach zwyklej i zarzadzanej.

  • Jesli stworzysz zas 'normalna' zmienna, nie-wskaznikowa, nie-referencyjna, to kompilator sam zarezerwuje miejsce, sam odpali ctor i masz gotowy obiekt pod reka. Wyparuje on gdy tylko zwiazana z nim zmienna wyjdzie poza scope. Tzn. zostanie wtedy wywolany destruktor tego, a miejsce ze stosu zwolni sie samo gdy funkcja/metoda sprzatnie swoja ramke ze stosu, albo zostanie uzyte na inna zmienna.

  • Jesli stworzysz zas "zwykle", niewskaznikowe, niereferencyjne pole klasy ---- to zachowuje sie ono tak, jak zmienna lokalna calego tego obiektu. Jej scopem jest obiekt-zawierajacy, on dostarcza miejsce na jej tresc. Obiekt-zawierany bedzie w srodku tego zawierajacego, i to doslownie. Jego konstruktor/destruktor zostanie wywolany automatycznie podczas konstrukcji/destrukcji obiektu ktory go zawiera. To kompletnie totalnie podstawowa cecha obiektow w C++, zerknij na pierwszy lepszy tutorial o konstruktorach i destruktorach w C++.

mikroskopijne wyjanienie znajdziesz nawet tutaj http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.11 (btw. to swietne miejsce zeby ot tak sobie poczytac 'ciekawostki')
tu masz troche wiecej o konstruktorach http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/cplr374.htm
do tych przykladow dopisz sobie w klasach destruktory i w nich umiesc jakies cout<<"jestem w destruktorze (odpowiednio)A/B/C" i zobaczysz co i jak sie wywoluje

0

Dzieki za te wszystkie wiadomosci, poczytam sobie to wnikliwie wszystko.
Juz mi sie zaczyna wszystko rozjasniac.

Pozdrawiam!

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