Pytanie o iterator klasy kontenerowej

0

Witam,

Nie moge zrozumiec pewnej kwestii zwiazanej z iteratorami klas kontenerowych. Podam przyklad:

Gdzies w programie mam zdefiniowana klase CShape, teraz chce utworzyc kontener wskaznikow na obiekty tej klasy:

vector<CShape*> Container;

oraz iterator:

vector<CShape*>::iterator Iter;

Nastepnie, gdzies dalej tworze obiekt klasy i dodaje do konterera jego wskaznik:

Container.push_back(new CShape(KULA)); // KULA to typ ENUM

Klasa CShape ma zdefiniowana miedzy innymi funkcje Draw();

I teraz moje pytanie: Dlaczego, skoro iterator jest wskaznikiem na dany obiekt swojej klasy nie moge zastosowac wywolania Iter->Draw(), lecz (*Iter)->Draw()?
Nie rozumiem skladni tego ostatniego zapisu

0

Iterator wcale nie jest wskaznikiem! Jest tylko imitacja wskaznika z przeciazonym operatorem *. Przeciazenie jest zrobione dla wygody. Gdy wywolujesz *Iter tak naprawde dopiero tutaj jest wyluskiwany wsaznik ktory przetrzymujesz w kontenerze, stad koniecznosc nawiasow i strzalki.

0

Ehm:
"An iterator is not a general pointer, but is an abstraction of the notion of a pointer."

Taka jest abstrakcja działania iteratorów, dereferencja iteratora powoduje dostęp do iterowanego obiektu. Ale to nie oznacza, że to jest bezpośredni dostęp... To jest referencja...
Dlatego są w STL algorytmy operujące na kontenerach :P
Jeżeli przechowujesz wskaźniki to licz się z takimi wygibasami z dostępem.

A zawsze można:

Container[0]->Draw();
0

Dzieki za wyjasnienie :) W takim razie mam jeszcze jedno pytanie: ktore rozwiazanie jest praktyczniejsze w odwolywaniu sie do obiektow w takim jak ten wypadkach? Zastosowanie iteratora, traktowanie konterera tak jak tablicy za pomoca [] czy tez moze jeszcze inne rozwiazanie?

0

Jak tylko sobie życzysz... Wszystkie są poprawne. Tylko z tymi wskaźnikami możesz mieć kłopoty, kasując wektor nie kasujesz wskazywanych obiektów...

Albo trochę hardcore'u w STL, przynajmniej widać jaka jest logika działania:

struct drawer
{
    void operator()(CShape &s) { s.Draw; }
};
//...
vector<CShape> vv;
//...
for_each( vv.begin(), vv.end(), drawer() );

Powodzenia.

0

Co do usuwania obiektow to mam to pod kontrola - kontener znajduje sie wewnatrz klasy, a w jej destruktorze umiescilem odpowiednia petle usuwajaca wskazywane obiekty. Natomiast biblioteke STL (zreszta C++ tez) dopiero poznaje i do hardcorowych zagrywek jeszcze nie doroslem ;)

Dzieki wielkie za polemike,
Pozdrowionka! :)

0
raybones napisał(a)

Co do usuwania obiektow to mam to pod kontrola - kontener znajduje sie wewnatrz klasy, a w jej destruktorze umiescilem odpowiednia petle usuwajaca wskazywane obiekty.

Tak, i co z tego? To nie jest takie proste, chyba że wziąłeś to pod uwagę ? :>

#define FORTIFY
#include "fortify.h"
#include <vector>

using namespace std;

class PtrArray
{
    vector<int*> w;
public:
    // dtor
    ~PtrArray() 
    { 
        //skasuj obiekty
        for (int i=0;i<w.size();i++) delete w[i];
    }
    
    void add(int *ptr)
    {
        w.push_back( ptr );
    }

    // symulujemy wyjatek w push_back()
    void addThrow(int *ptr)
    {
        //w.push_back( ptr );
        throw "error";
        /*
           push_back() nie zostalo wykonane,
           np. nastapila alokacja pamieci na powiekszony bufor
               i zabraklo pamieci...
              wszystko działo się w push_back()
              na szczescie wektor w nie jest zmieniony.
        */
    }
};

int main()
{
    Fortify_EnterScope();
    try
    {
        PtrArray a;
        
        a.add( new int );
        a.add( new int );
        a.add( new int );
        
        int *p;

        // ## test 1 ##
        // alokacja zewnetrzna
[EDITO:
yyy trochę może nie tak to jest w tym miejscu... o czymś innym myślałem.
ale załóżmy, że mamy wyjątek przed dodaniem wskaźnika do listy
co oczywiście nie jest błędem samego PtrArray
}
        p = new int; throw "bad alloc";
        // "coś" :) failed...
        a.add(p);

// ale tu na pewno jest błąd PtrArray
        // ## test 2 ##
        // alokacja udana albo wewnetrzna nawet
        p = new int;
        a.addThrow(p);
        // add() failed! -> push_back() failed -> BAD PtrAraay dtor
    }
    catch( ... )
    {
    }
    Fortify_LeaveScope();
    Fortify_OutputStatistics();
}

Sorry za trochę kodu ale inaczej się nie da...

Test 2 wyjście:

Fortify: Memory leak detected leaving scope at b.cpp.63
   Address     Size Allocator
  004A2478        4 b.cpp.56
     total        4 bytes in 1 blocks with 112 bytes overhead

Oczywiście bez tych sytuacji wyjątkowych wszystko jest OK:

Fortify: Statistics at b.cpp.66
         Memory currently allocated: 0 bytes in 0 blocks

Dlatego to są tak cięzkie rodzaje błędów w projekcie programu...

Radzę zajrzeć na: http://www.relisoft.com/resource/auto_vector.html
Chyba, że NAPRAWDĘ wiesz na co się piszesz używając gołych wskaźników :D

0

mmm, ciekawa dyskusja sie robi :) co prawda, troche sporo jeszcze wiedzy mi brakuje, ale postaram sie nie poddawac latwo :>

wlasnie dzis czytalem, ze w wypadku stosowania wskaznikow powstaje problem podczas wyrzucania wyjatkow, bo kompilator sam nie zwolni obszaru ktory one wskazuja, i wowczas zaleca sie opakowanie wskaznikow w klase i przeciazenie operatorow & oraz *, co daje gwarancje ze jakkolwiek wystapi wyjatek, tak wskaznik opakowany w klase z pewnoscia zostanie zwolniony razem z obszarem ktory zajmuje.

a poza tym, czyz nie po to zostal stworzony dodatkowy blok uzupelniajacy do try -catch - mam na mysli oczywiscie finally ? jesli taki blok zastosujemy chocby w main (rzecz jasna ze sami wybierzemy wlasciwe miejsce) to czy nie zagwarantujemy sobie ze ten caly wczesniej utworzony zasob zostanie zwolniony?

p.s. polecona lekture oczywiscie przerobie - tylko ciekawe z jakim skutkiem ;)

0
raybones napisał(a)

wlasnie dzis czytalem, ze w wypadku stosowania wskaznikow powstaje problem podczas wyrzucania wyjatkow, bo kompilator sam nie zwolni obszaru ktory one wskazuja, i wowczas zaleca sie opakowanie wskaznikow w klase i przeciazenie operatorow & oraz *, co daje gwarancje ze jakkolwiek wystapi wyjatek, tak wskaznik opakowany w klase z pewnoscia zostanie zwolniony razem z obszarem ktory zajmuje.

Jeżeli chodzi o jeden wskaźnik to sam pomysł jest OK, tak samo jak pomysł z trzymaniem wektora wskaźników ALE w implementacji musi nastąpić przekazanie odpowiedzialności za wskaźnik... W przypadku pojedynczego wskaźnika wymyślono klasę

auto_ptr

, która jest w STL i wtedy wskaźnik JEST bezpieczny, gdyż nastąpi dealokacja obiektu w trakcie destrukcji obiektu auto_ptr. Dla tablic OBIEKTÓW wymyślono vector

, który też jest opakowaniem na wskaźnik (do bufora). Cały problem polega właśnie na przekazywaniu odpowiedzialności, bo jeśli chce mieć wektor wskaźników to w sytuacji przedstawionej przeze mnie powyżej przekazuję wskaźnik ale nie-opakowany i to jest miejsce bardzo wrażliwe, dlatego w przypadku wyjątku tracimy kontrolę nad wskaźnikiem, uff :) Po prostu ze wskaźnikami są większe problemy niż się przeważnie sądzi, szczególnie w środowisku, gdzie są wyjątki.
Rozwiązaniem tablicy wskaźników jest właśnie 
```cpp
auto_vector

z Relisoft

raybones napisał(a)

a poza tym, czyz nie po to zostal stworzony dodatkowy blok uzupelniajacy do try -catch - mam na mysli oczywiscie finally ? jesli taki blok zastosujemy chocby w main (rzecz jasna ze sami wybierzemy wlasciwe miejsce) to czy nie zagwarantujemy sobie ze ten caly wczesniej utworzony zasob zostanie zwolniony?

To nie jest standard. Musiałbyś i tak KAŻDY alokowany obiekt obsługiwać...
-> http://www.research.att.com/~bs/bs_faq2.html#finally

raybones napisał(a)

p.s. polecona lekture oczywiscie przerobie - tylko ciekawe z jakim skutkiem ;)

Cały ten site (relisoft.com) jest naprawdę wart polecenia :)

0

Sugerujesz wiec, ze moze lepiej trzymac w vectorze lokalne kopie obiektow i na nich dzialac? Tyle ze znowu to rozwiazanie ma ta zasadnicza wade ze w momencie kiedy zajdzie potrzeba poszerzenia zajmowanej przez vector przestrzeni pamieci, vector szuka wiekszej CIAGLEJ pamieci i tam przenosi dotychczasowe zasoby. Ponoc jest to malo efektywne... ale pewnie jak znam zycie lepiej bedzie jak sam to sprawdze :)
Przeszlo mi tez przez mysl skorzystanie z innego kontenera np. list, ale to na pewno nie wchodzi w gre, poniewaz caly problem nie lezy u podstaw kontenera tylko w wyjatkach.

Poczytam jeszcze co o tym pisze Bruce Eckel w "Thinking in C++" (w drugim tomie jest caly rozdzial o kontenerach).

p.s. Rowniez innym czytajacym ten watek polecam bardzo fajnie napisany artykul o wyjatkach (bardzo obszerny i w calosci po polsku): http://avocado.risp.pl/files/texts/od0dogk/html/2_3.html

0
raybones napisał(a)

Sugerujesz wiec, ze moze lepiej trzymac w vectorze lokalne kopie obiektow i na nich dzialac? Tyle ze znowu to rozwiazanie ma ta zasadnicza wade ze w momencie kiedy zajdzie potrzeba poszerzenia zajmowanej przez vector przestrzeni pamieci, vector szuka wiekszej CIAGLEJ pamieci i tam przenosi dotychczasowe zasoby. Ponoc jest to malo efektywne... ale pewnie jak znam zycie lepiej bedzie jak sam to sprawdze :)
[...]

Nie sugeruję... http://klub.chip.pl/b.krzemien/c++-faq-pl/big-picture.html#faq-6.15 "Pisanie Programów Jest Podejmowaniem Decyzji"
Tu nie chodzi o to czy będą to obiekty czy wskaźniki w wektorze, tylko o to żebyś zdawał sobie sprawę co może się złego stać jeżeli do wektora dodajesz gołe wskaźniki (jak i samo to, że je przechowujesz) i co ważniejsze oddajesz mu odpowiedzialność za nie.

No właśnie, czy wektor obiektów to taki zły pomysł?? A ile tych alokacji powiększających wektor myślisz, że będzie?? A czy przy wektorze wskaźników nie ma alokacji? Zawsze można zrobić reserve() :)

A tak naprawdę obniżając jeszcze bardziej abstrakcję przechowywania wskaźników to wystarczy przecież tablica wskaźników i to nawet statyczna, a i tak w przypadku (poważnego) błędu można olać dealokację... :/

0
marcinEc napisał(a)

Nie sugeruję... http://klub.chip.pl/b.krzemien/c++-faq-pl/big-picture.html#faq-6.15 "Pisanie Programów Jest Podejmowaniem Decyzji"
Tu nie chodzi o to czy będą to obiekty czy wskaźniki w wektorze, tylko o to żebyś zdawał sobie sprawę co może się złego stać jeżeli do wektora dodajesz gołe wskaźniki (jak i samo to, że je przechowujesz) i co ważniejsze oddajesz mu odpowiedzialność za nie.

Dzieki za zwrocenie na to uwagi - jakze bloga nieswiadomosc moze byc zgubna :)

marcinEc napisał(a)

No właśnie, czy wektor obiektów to taki zły pomysł?? A ile tych alokacji powiększających wektor myślisz, że będzie?? A czy przy wektorze wskaźników nie ma alokacji? Zawsze można zrobić reserve() :)

Pewnie ze alokacja jest, tylko ile pamieci wezmie wskaznik a ile cala klasa ;)

marcinEc napisał(a)

A tak naprawdę obniżając jeszcze bardziej abstrakcję przechowywania wskaźników to wystarczy przecież tablica wskaźników i to nawet statyczna, a i tak w przypadku (poważnego) błędu można olać dealokację... :/

To rozwiazanie faktycznie wydaje sie wrecz bajecznie proste i na dodatek bez wnikania w niuanse budowy i dzialania kontenerow, ale tutaj tez da sie w razie wypadku we znaki dzialanie obslugi wyjatkow :(

Odnoszac sie do akapitu "Pisanie Programów Jest Podejmowaniem Decyzji" to tez cenna uwaga, ktora zlagodzi w przyszlosci niejeden dylemat natury technicznej :d
Mysle ze nalezy Ci sie solidne [browar] :)

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