[C++] Zmodyfikowany Obserwator (wzorzec projektowy) 7548 11

O temacie

Autor Wonski

Zaczęty 28.02.2016 roku

Wyświetleń 7548

Odpowiedzi 11

Wonski

Wonski

Gry (themodders@telegram)
radio engineer
posty256
Propsy91
ProfesjaProgramista
  • Gry (themodders@telegram)
  • radio engineer

Wonski
Gry (themodders@telegram)

[C++] Zmodyfikowany Obserwator (wzorzec projektowy)
2016-02-28, 18:27(Ostatnia zmiana: 2016-02-28, 20:02)
Cześć wszystkim

Ostatnio chciałem w moim projekcie zaimplementować wzorzec projektowy Observer. Sama implementacja jest banalna, ale wzorzec w surowej wersji raczej modeluje relację wiele klas_obserwatorów -> jedna klasa_obserwowana.
Przykład implementacji wzorca obserwator:

class Observer
{
public:
virtual void Update() = 0;
};

class Subject
{
std::list<Observer*> _observers;

public:
virtual void Attach(Observer* o)
{
_observers.push_back(o);
};

virtual void Detach(Observer* o)
{
_observers.remove(o);
};

virtual void Notify()
{
for (Observer *j : _observers)
j->Update();
};
};

oraz przykładowa implementacja na przykładowych klasach:

class klasa_obserwowana :public Subject
{
public:

void funkcja_klasa_obserwowana()
{
std::cout << "funcja wywołana z klasa_obserwowana" << std::endl;
Notify();
}
};

class obserwator_konkretny :public Observer
{
klasa_obserwowana *obserwowana;

public:
obserwator_konkretny(klasa_obserwowana *klasa_oberw)
{
obserwowana = klasa_oberw;
}

void funcja_klasa_obserwujaca()
{
std::cout << "funkcja wywolana z obserwator_konkretny" << std::endl;
}

void Update()
{
funcja_klasa_obserwujaca();
}
};


Problem jest w tym, że gdybym chciał by klasa obserwator_konkretny obserwowała wiele obiektów klasy klasa_obserwowana to musiałbym wprowadzić swego rodzaju ID dla tychże obiektów.

Może lepiej to zobrazuję.
Klasa obserwator_konkretny przechowuje na liście czy w kontenerze wskaźniki do obiektów klasa_obserwowana.
Niech obiekt klasa_obserwowana wywołuje metodę attach w momencie gdy klasa obserwator_konkretny umieści go na liście, a wywołuje detach w tym samym momencie kiedy wywoła Notify.
I tu właśnie jest problem, ponieważ metoda notify wywołuje Update dla obserwator_konkretny i jeżeli w tym momencie chciałbym przestać obserwować obiekt to nie wiem który....
Myślałem właśnie by wprowadzić jakiś primary key dla klasa_obserwowana i jakoś go przekazać poprzez notify do obserwatora_konkretnego, ale jak na razie nie mama pojęcia jak to zrobić..

Pozdrawiam i czekam na odpowiedzi,

edit
W sumie można by to zrobić w ten sposób, że metodę notify w klasie Subject zrobić jako czysto wirtualną i przesłonić ją dopiero w implementacji tzn, klasa_obserwowana i dopiero tam kombinować z tym atrybutem ID.
Jednak na razie poczekam na Wasze opinie



Post połączony: 2016-02-28, 19:25
Ok pokombinowałem chwilę i pomysł z id nie jest całkowicie pozbawiony sensu. Problemem natomiast jest fakt, że nie jedna klasa może posiadać id, przykład:

class A
{
int id;
};

class B
{
int id;
};

class C
{
int id;
};

Natomiast sam wzorzec projektowy zmodyfikowałem w następujący sposób:
class Observer
{
public:
virtual void Update(int id) = 0;
};

class Subject
{
std::list<Observer*> _observers;

public:
virtual void Attach(Observer* o)
{
_observers.push_back(o);
}
virtual void Detach(Observer* o)
{
_observers.remove(o);
}
virtual void Notify() = 0;

};

Gdzie po implementacji jak w poprzednim poście:

class klasa_obserwowana :public Subject
{
         int id;
public:
        virtual void Notify() override
{
for (Observer *j : _observers)
j->Update(this->get_id());
};

void funkcja_klasa_obserwowana()
{
std::cout << "funcja wywołana z klasa_obserwowana" << std::endl;
Notify();
}
};

class obserwator_konkretny :public Observer
{
klasa_obserwowana *obserwowana;

public:
obserwator_konkretny(klasa_obserwowana *klasa_oberw)
{
obserwowana = klasa_oberw;
}

void funcja_klasa_obserwujaca()
{
std::cout << "funkcja wywolana z obserwator_konkretny" << std::endl;
}

void Update(int id)
{
                // inny wektor np a1
                // a1.remove(get_object_by_id(id);
funcja_klasa_obserwujaca();
}
};

Jednak nadal problemem pozostaje do kogo należy to id? Do klasy A, B czy C.

Myślałem, by w każdej z tych klas dać wartość static const i przekazywać ją w update, ale nie wiem czy to jest optymalne rozwiązanie.
Oczywiście w ostateczności można przesłać cały obiekt lub wskaźnik do niego, ale to już bardzo komplikuje sprawę, ponieważ musiałbym albo tworzyć szablon i tako typename podawać każdą z możliwych klas, po drugie tracę sporo na obiektowości stosując takie rozwiązanie.
Najlepszym rozwiązaniem wydaje się by właśnie wprowadzić atrybut static const, a samą metodę update zrobić jako dwuargumentową i przekazywać id oraz tego consta, by następnie za pomocą switcha precyzować o którą klasę chodzi, lecz i to nie jest do końca satysfakcjonujące rozwiązanie.
 

inż. Avallach

inż. Avallach

Administrator
posty7661
Propsy5239
NagrodyV
ProfesjaProgramista
  • Administrator
Problem jest w tym, że gdybym chciał by klasa obserwator_konkretny obserwowała wiele obiektów klasy klasa_obserwowana to musiałbym wprowadzić swego rodzaju ID dla tychże obiektów.
Nie, to nie tak. Nie rozumiem dokładnie twojej intencji (za mało na jej temat napisałeś), ale ogólnie są dwie możliwości.

Jeśli wszystkie obiekty obserwowane są obserwowane prze obserwatora "z tego samego powodu" to nie ma potrzeby ich rozróżniać. Możesz ewentualnie w parametrze metody obserwującej przekazać dodatkowe informacje o wydarzeniu które wystąpiło. To typowe we wzorcu obsever, parametr ten zwykle nosi nazwę 'event'. Ale dopóki domena problemu tego nie wymusza, nie wprowadzaj bez potrzeby rozróżniania od siebie instancji ani implementacji. To co jest kluczowe to zachowania (implementowane interfejsy).

Jeśli są obserwowane z różnych powodów, to powinieneś użyć różnie nazwanych dedykowanych do konkretnych interfejsów obserwatorów, z różnie nazwanymi metodami. Wtedy jeden obserwator będzie implementował wiele różnych "interfejsów obserwatora". Każda z klas obserwowanych będzie wiedziała tylko tyle że jest on "obserwatorem tego za co ona odpowiada" i wywoływała na nim związaną z tym metodę dedykowanego jej interfejsu obserwatora.

Przykładowo: chcesz mieć obserwowalne zdarzenia "przyszedł sprzedawca kanapek" i "zamawiamy obiad".
Dla każdego z nich mogą się zarejestrować obserwatorzy, i kiedy wystąpi, otrzymać notyfikację. Dość oczywiste jest że potrzebujemy tutaj dwóch różnych interfejsów obserwatora z różnymi sygnaturami metody notyfikującej (bo różne będą ewentualnie przekazywane w parametrze dodatkowe opisy tych eventów).
Możesz mieć obiekt który będzie obserwatorem dla obu tych zdarzeń na raz. Nie ma potrzeby wprowadzania żadnych ID, bo o obu zdarzeniach jest informowany za pomocą różnych metod.


Wonski

Wonski

Gry (themodders@telegram)
radio engineer
posty256
Propsy91
ProfesjaProgramista
  • Gry (themodders@telegram)
  • radio engineer

Wonski
Gry (themodders@telegram)

[C++] Zmodyfikowany Obserwator (wzorzec projektowy)
#2 2016-02-29, 15:48(Ostatnia zmiana: 2016-02-29, 16:14)
No faktycznie kiepsko to wytłumaczyłem o co mi chodzi..
Mamy klasy A, B oraz C

class A
{
int id;
};

class B
{
int id;
};


gdzie klasa C ma zawierać dwa kontenery wskaźników, osobny dla klasy A i B:
class C
{
std::vector < A* > A_ptr_container;
std::vector < B* > B_ptr_container;
};

// Opis klas jest oczywiście bardzo uproszczony, ale do dla Twojej wygody :)

Taka budowa bardzo mi odpowiada, ponieważ modyfikując osobno obiekty A i B nie muszę modyfikować ich reprezentacji w klasie C, ponieważ dzieje się to automatycznie poprzez wskaźnik. Tak samo na odwrót, jeżeli w klasie C chcę zmodyfikować atrybut obiektu klasy A lub B to odwołuję się do nich przez wskaźnik.
Proste.
Ale....
Jeżeli wywołam destruktor na obiekcie klasy A lub B, którego wskaźnik znajduje się w klasie C, to skąd obiekt klasy C ma wiedzieć czy takie wydarzenie miało miejsce? Co więcej skąd ma wiedzieć do której klasy należy dany obiekt i jakie ma ID (wprowadziłem założenie, że każdy obiekt ma być unikalny)?
No właśnie nie wie i gdybym chciał się odwołać przez wskaźnik znajdujący się w C do obiektu A lub B to wywołałbym wyciek pamięci i prawdopodobnie crash całego programu.

I tutaj właśnie z pomocą przychodzi mi Observer.
Ostatnio go lekko zmodyfikowałem i oto co udało mi się zrobić:
class Subject;

class Observer
{
public:
virtual bool Update(Subject * helper) = 0;
};

class Subject
{
protected:
std::list<Observer*> _observers;

public:
virtual void Attach(Observer* object)
{
_observers.push_back(object);
}
virtual void Detach(Observer* object)
{
_observers.remove(object);
}
virtual void Notify() = 0;

};

Jest to inna implementacja niż standardowa (czysto wirtualna metoda Notify()) co ma swoje ogromne zalety.
Zaimplementujmy więc Observer do klasy C i Subject do klasy A (B już pomijam bo to analogiczna sytuacja)

class A :public Subject
{
int id;

virtual void Notify() override
{
for (Observer *j : _observers)
j->Update(this);
}
};

Gdzie samą metodę notify() wywołuję w destruktorze klas A i B, by powiadomić klasę C o usunięciu obiektu, następnie metodą Update (w klasie implementującej Observera) usuwam obiekt z kontenerów, dzięki czemu zapobiegam odwołaniu sie do pustego/błędnego wskaźnika pozostałego po usunięciu obiektu A i B.

Samą metodę Attach na obiekcie klasy A lub B wywołuję w metodzie klasy C (już nie pisałem jest tutaj), która nazywa się add_A(A helper) lub add_B(B helper).
Wydaje mi się to proste i zrozumiałe. No bo chcę by obserwowanie obiektu klasy A zaczęło się w trakcie wrzucenia go do kontenera obiektu klasy C a zakończyło się z chwilą jego wyrzucenia stamtąd .

W obserwatorze natomiast muszę dodać pole w klasie odpowiadające za pomocnicze wskaźniki klas A i B.

class C :public Observer
{
std::vector < A* > A_ptr_container;
std::vector < B* > B_ptr_container;

A* A_helper;
B* B_helper;

virtual void Update(Subject * helper) override
{
if (helper == A_helper)
{
if (this->remove_A(helper->get_ID_A()));
return SUCCESS;
else
return ERROR;
}

else if (helper == B_helper)
{
if (this->remove_B(helper->get_ID_B()));
return SUCCESS;
else
return ERROR;
}
else
return ERROR;
};
};

I teraz muszę przekazywać albo wskaźnik do obiektu (tj to teraz zrobiłem) albo jak myślałem wcześniej, przekazywać dwa argumenty, tzn ID oraz unikalną dla klasy wartość static const jednoznacznie identyfikującą klasę.
Jednak po odwołaniu się do elementarnej wiedzy, dowiadujemy się, ze wskaźnik to są 4 bajty, więc rozwiązanie w dwoma argumentami jest cholernie nieoptymalne (całe piękno wskaźników w c++  :cool: ).

No bo jak inaczej klasa C ma rozpoznać ID obiektu, który przestał powiadomienie i który trzeba usunąć z kontenera? Co więcej jak rozpoznać klasę tego obiektu a chciałem nadmienić, że w moim projekcie jest przewidziane miejsca dla właśnie dwóch klas, ale może być ich znacznie więcej i co wtedy?

Chcę wiedzieć co sądzisz o takim rozwiązaniu, czy jest ono optymalne czy jednak można zastosować jakąś inną technikę lub wzorzec.
 

inż. Avallach

inż. Avallach

Administrator
posty7661
Propsy5239
NagrodyV
ProfesjaProgramista
  • Administrator
Nie mam teraz czasu tego całego czytać, ale już na początku widzę dwa istotne problemy
 - destruktura się nie wywołuje ("Jeżeli wywołam destruktor na obiekcie")
 - jeśli destruktor (albo konstruktor) ma efekty uboczne, to Wiedz, Że Coś Się Dzieje (zwykle oznacza pójście na łatwiznę w którymś etapie designu i problemy później). "Odrejestrowywanie" powinieneś przeprowadzać ręcznie, nie w destruktorze.

Najlepiej w ogóle olej ręczne zarządzanie pamięcią o ile nie kodzisz czegoś o niesamowitych wymaganiach wydajnościowych (sam w tej chwili kodzę w C++ oprogramowanie przemysłowe i używamy unique_ptr i shared_ptr obowiązkowo - ręczne zarządzanie pamięcią nie daje wymiernych dla nas zysków, a znacząco spowalnia pisanie kodu i stwarza potencjał do popełniania katastrofalnych pomyłek).

Potem przeczytam i skomentuję całość. Ogólnie mam wrażenie że twoje podejście jest złe. Takie rzeczy da się robić w Javie czy C#, gdzie mamy do dyspozycji refleksję (a co za tym idzie wbudowane rozpoznawanie ID i klas obiektów), ale nawet tam jest to w niemal wszystkich przypadkach bardzo złe rozwiązanie.

Wonski

Wonski

Gry (themodders@telegram)
radio engineer
posty256
Propsy91
ProfesjaProgramista
  • Gry (themodders@telegram)
  • radio engineer

Wonski
Gry (themodders@telegram)

[C++] Zmodyfikowany Obserwator (wzorzec projektowy)
#4 2016-02-29, 16:30(Ostatnia zmiana: 2016-02-29, 16:53)
Nie mam teraz czasu tego całego czytać, ale już na początku widzę dwa istotne problemy
 - destruktura się nie wywołuje ("Jeżeli wywołam destruktor na obiekcie")

Nie rozumiem tego za bardzo...
Przecież destruktor mogę wywołać jawnie lub niejawnie.
Niejawnie w przypadku kiedy obiekt został utworzony dynamicznie za pomocą new, wtedy destruktor wywołujemy, niejawnie oczywiście, poprzez delete.
No a jawnie to mogę prosto z mostu:
A a1;
a1.~A();


- jeśli destruktor (albo konstruktor) ma efekty uboczne, to Wiedz, Że Coś Się Dzieje (zwykle oznacza pójście na  łatwiznę w którymś etapie designu i problemy później)

No być może tak jest  :D, ale teraz rozpatrujemy ten konkretny przypadek. i w tym konkretnym przypadku "skutki uboczne" są oczywiste i uzasadnione.
Zresztą nawet sama nazwa sugeruje, że destruktor ma "posprzątać" po usuniętym obiekcie. W tym przypadku jest to jego wskaźnik, który tylko zajmuje miejsce i stwarza okazję do crashowania programu.
A co do reszty to może faktycznie lepiej doczytaj na spokojnie.


Dzięki za odzew i pozdrawiam
 

inż. Avallach

inż. Avallach

Administrator
posty7661
Propsy5239
NagrodyV
ProfesjaProgramista
  • Administrator
Wywołanie destruktora jest automatycznie dodawane przez kompilator tam gdzie obiekt był alokowany na stosie i kontrola opuszcza jego scope, albo kiedy używamy operatora delete. Ręczne wywoływanie destruktora to kolejny znak że robisz coś bardzo źle.

Destruktor ma zwolnić zasoby.
Derejestracja z wszelkiego rodzaju obserwatorów jest czymś co powinno zostać zrobione przez *usunięciem* obiektu. Usuwanie czegoś co jest gdzieś porejestrowane, nawet jeśli liczysz na to że derejestracją zajmie się destruktor tego czegoś, to naprawdę nie najlepszy pattern. Chociaż oczywiście nie twierdzę że "nie będzie działać".

Musisz albo bardzo gruntownie przemyśleć problem ownershipu i odpowiedzialności związanych z tymi rejestracjami, albo (jak już pisałem) przejść na automatyczne zarządzanie pamięcią.

BTW, jak to jest że w virtual void Update(Subject * helper) override robisz returny?
Do tego zwracasz "ERROR" lub "SUCCESS", co oznacza że albo korzystasz z opartej o komunikaty komunikacji z jakimś zewnętrznym API, albo idziesz w wyjątkowo szkodliwy pattern dla ludzi którzy nie potrafią poprawnie korzystać z obsługi wyjątków.

Wonski

Wonski

Gry (themodders@telegram)
radio engineer
posty256
Propsy91
ProfesjaProgramista
  • Gry (themodders@telegram)
  • radio engineer

Wonski
Gry (themodders@telegram)

[C++] Zmodyfikowany Obserwator (wzorzec projektowy)
#6 2016-02-29, 17:45(Ostatnia zmiana: 2016-02-29, 17:51)
BTW, jak to jest że w virtual void Update(Subject * helper) override robisz returny?
Do tego zwracasz "ERROR" lub "SUCCESS", co oznacza że albo korzystasz z opartej o komunikaty komunikacji z jakimś zewnętrznym API, albo idziesz w wyjątkowo szkodliwy pattern dla ludzi którzy nie potrafią poprawnie korzystać z obsługi wyjątków.

Blehhh, to miał być bool. Robiłem copy pasta z projektu i nie podmieniłem.
SUSCCESS i ERROR to są makra.



Doczytam o tym automatycznym zarządzaniu za pomocą unique_ptr, shared_ptr bo przyznam, że wcześniej o tym nie słyszałem.

No to powiedz mi w takim razie w jaki sposób można rozwiązać ten problem? Tzn jak w innym sposób poinformować obiekt C, że obiekt A jest właśnie w tym momencie usuwany jak nie za pomocą destruktora.
Kurde no, mam wrażenie, że nie za bardzo rozumiesz o co mi chodzi..

Cytuj
Derejestracja z wszelkiego rodzaju obserwatorów jest czymś co powinno zostać zrobione przez *usunięciem* obiektu.
No właśnie destruktor jest wywoływany przed a nie w trakcie usuwania obiektu.
 

inż. Avallach

inż. Avallach

Administrator
posty7661
Propsy5239
NagrodyV
ProfesjaProgramista
  • Administrator
jak w innym sposób poinformować obiekt C, że obiekt A jest właśnie w tym momencie usuwany jak nie za pomocą destruktora.
W żaden. Brutalnie mówiąc: chuj mu do tego. W normalnych sytuacjach żaden obiekt w C++ (w jakimkolwiek języku który przychodzi mi do głowy?) nie musi być informowany że jakiś inny jest tworzony czy usuwany (chyba że implementujesz pulę pamięci czy inne bardzo specjalne mechanizmy). Dlatego też próbuję ci wytłumaczyć że robienie takich rzeczy zmieniających stan programu w destruktorze to prawdopodobnie zły design.

Wydaje mi się że najprościej będzie ci to przedstawić za pomocą symetrii.
Gdzie dokonujesz rejestracji?
W konstruktorze? Nie, to byłby bardzo zły pattern - efekty uboczne w konstruktorze to proszenie się o problemy (choćby przy pisaniu testów jednostkowych).
Robisz to wywołując metodę Attach w jakimś kodzie który zajmuje się obsługą obiektu A i C i już zrobił A.

Gdzie dokonasz derejestracji?
W destruktorze, czy w kodzie który zajmuje się obsługą obiektu A i C i dopiero później usunie A?

BTW, używanie patternu w którym zwracasz z metody wartości oznaczające "success" / "error", a do tego makr, świadczy że uczyłeś się C++ albo z bardzo starego kursu, albo od bardzo nieogarniętego prowadzącego, albo masz silne nawyki z C.

Wonski

Wonski

Gry (themodders@telegram)
radio engineer
posty256
Propsy91
ProfesjaProgramista
  • Gry (themodders@telegram)
  • radio engineer

Wonski
Gry (themodders@telegram)

[C++] Zmodyfikowany Obserwator (wzorzec projektowy)
#8 2016-02-29, 18:56(Ostatnia zmiana: 2016-02-29, 19:28)
Rozumiem, chyba.

Czyli mówisz, że Observer w tym przypadku jest zbędny (no bo skoro informowanie klasy C o zmianie stanu A jest zbędne to nie widzę sensu w implementacji observera) i można to zrobić za pomocą inteligentnych (chyba tak się to nazywa) wskaźników, tak?

Jeżeli dobrze Cię zrozumiałem to zamiast zwykłego wskaźnika w klasie C powinienem przechowywać unique_ptr obiektu A. Jeżeli obiekt A zostanie usunięty to to samo automatycznie dzieje się z wskaźnikiem, dobrze to rozumiem?
No ale w takim razie problem pozostaje jeszcze taki aspekt, że unique_ptr dla danego obiektu A może istnieć tylko jeden, więc wyklucza to zastosowanie tego wskaźnika, ponieważ nie mógłbym go umieścić jednocześnie w innych obiektach klasy C.

No więc pozostaje rozwiązanie z użyciem shared_ptr. Czy jest tak, że wraz z usunięciem obiektu A automatycznie zostają usunięte także obiekty shared_ptr?
Jeżeli tak jest to problem praktycznie rozwiązany i Observer jest zbędny.
 

inż. Avallach

inż. Avallach

Administrator
posty7661
Propsy5239
NagrodyV
ProfesjaProgramista
  • Administrator
Mniej więcej zrozumiałeś jak działają wskaźniki - tylko że dokładnie na odwrót ;)

To nie jest tak że jak usuniesz obiekt, to wskaźnik z listy też zniknie.
To jest tak że jak usuniesz wskaźnik z listy, to obiekt też zniknie.

Nie mówię że obserwator jest zbędny. Mówię tylko że nie powinieneś opierać go o destruktory ani konstruktory. Powinieneś informować obserwatora za pomocą innych metod.

Ogólnie w C++ nie ma żadnego sposobu żeby sprawdzić czy obiekt znajdujący się pod danym wskaźnikiem (nie ważne czy "inteligentnym" czy surowym) został usunięty. Trzymanie listy wskaźników na istniejące obiekty jak najbardziej ma sens, zwłaszcza jeśli będą to wskaźniki inteligentne. Jeśli będą to unique_ptry, to w momencie kiedy wyrzucisz któregoś z listy, obiekt na który wskazywał zostanie też usunięty. Z shared_ptrami jest trochę inaczej - obiekt na który wskazują będzie istniał, dopóki gdzieś istnieje przynajmniej jeden shared_ptr na niego wskazujący ("reference counting"). Z tego względu są dużo wygodniejsze w użyciu, chociaż kiedy chce się utrzymać porządek i dobrze rozumieć jaki jest "czas życia" obiektów lepiej jest używać unique_ptrów.

Nie dam ci gotowego przepisu, bo to wymagałoby ode mnie sporo wysiłku związanego ze zrozumieniem tego co naprawdę robisz. Teraz widzę i rozumiem tylko fragmenty twojego designu, zakładam że te do których moje komentarze mogą najwięcej pomóc.

Wonski

Wonski

Gry (themodders@telegram)
radio engineer
posty256
Propsy91
ProfesjaProgramista
  • Gry (themodders@telegram)
  • radio engineer

Wonski
Gry (themodders@telegram)

[C++] Zmodyfikowany Obserwator (wzorzec projektowy)
#10 2016-03-27, 23:02(Ostatnia zmiana: 2016-03-28, 16:47)
@inż. Avallach
Z miejsca przepraszam za odkop, ale mam pytanie...

...odnoście smart_pointerów.
Pisałeś, że zabawa w ręczne zarządzanie pamięcią to gra nie warta świeczki i zamiast tego lepiej przerzucić się na wyżej wspomniane smarty...

Ale myśląc na zdrowy rozum, olanie ręcznej obsługi pamięci, biorąc pod uwagę tylko wydajność (no i wygodę), to raczej błędne rozumowanie, bo jak dobrze wiesz wszystko rozbija się o skalę.
Co innego zaalokować pamięć pod 100 000 obiektów za pomocą new a co innego za pomocą make_shared.
Gdy porównywałem w debuggerze alokację przy użyciu new to trwa to cholernie krócej niż w przypadku shared.
Nawet podglądając krok po kroku wykonywanie kodu to znowu kod oparty na inteligentnych wskaźnikach trwa no po prostu zdecydowanie dłużej.
shared_ptr czy unique_ptr to tylko opakowanie na wskaźnik, ale jednak jest to obiekt i zdecydowanie większy niż wskaźnik 4 bajtowy.

Czy to rzeczywiście jest gra niewarta świeczki?
Wg mnie stosowanie w takich przypadkach smart pointerów to overkill i wyraźne marnowanie zasobów.
 

inż. Avallach

inż. Avallach

Administrator
posty7661
Propsy5239
NagrodyV
ProfesjaProgramista
  • Administrator
Są sytuacje w których czasu procesora masz więcej niż potrzebujesz. A spowolnienie rozwoju kodu i ryzyko popełnienia błędów w ręcznym zarządzaniu pamięcią wiążą sie z bardzo wysokimi i bardzo konkretnymi kosztami.
Jeśli musisz alokować pamięć pod 100 000 obiektów i jest to jakaś krytyczna sekcja twojego programu która musi być maksymalnie szybka, to masz problem gorszy niż taki w którym wystarczy użyć surowego new. Ono też bywa zbyt wolne. W tego typu sytuacjach ja sięgam po pule pamięci / pule obiektów.
W typowych sytuacjach, smart pointery są bardzo dobrym kompromisem pomiędzy wydajnością a wygodą i niezawodnością. Stosuje się je powszechnie w aplikacjach przemysłowych - między innymi u mnie w firmie.

Oczywiście nie wszędzie można sobie pozwolić na ich stosowanie. Ale tam gdzie ograniczenia wydajnościowe są tak duże, to często nie można nawet użyć C++ z jego polimorfizmem opartym o vtable itd. I u mnie w firmie są takie obszary - tam kodzi się w gołym C. W krytycznych przypadkach robi się wstawki assemblerowe.

Ogólnie - są różne rozwiązania przystosowane do różnych wymagań wydajnościowych. Smart pointery są dobre tam, gdzie te wymagania nie są krytyczne - czyli choćby aplikacje desktopowe, które ty zapewne chcesz tworzyć.

Swoją drogą "cholernie krócej" czy "zdecydowanie dłużej" nic nie mówi. Wydaje mi się że różnica w alokacji nie powinna być właściwie duża. Shared pointery rzeczywiście są mniej wydajne od surowych, ale wynika to z tego że mają destruktor, który dekrementuje współdzielony reference counter i porównuje jego wartość z zerem.


0 użytkowników i 1 Gość przegląda ten wątek.
0 użytkowników
Do góry