opóźnienie wywołania konstruktora obiektu wewnętrznego

0

taki problem mi się narodził, dość prymitywny i szczerze mówią nie mam pomysłu jak to rozwiązać tak, żeby nie łamać wszystkich zasad OPP :> :

mam dwie hipotetyczne klasy:

//wewnętrzna posiada konstruktor 2 argumentowy, domyślny niedozwolony
class base { public: base(typ x,typ y); protected: base(); [...] };

//klasa zewnętrza zawiera klasę base
class outside { public: outside(takie tam); protected: base obiekt_wew; [...] }; 

i teraz jest taka akcja:
-klasa outside przy tworzeniu musi wywołać konstruktor dla obiektu obiekt_wew, należy to zrobić w liście inicjalizacyjnej, to wiadomo, i tu się pojawia ale, otóż przed wywołaniem konstruktora obiekt_wew trzeba obliczyć dla niego parametry, tylko to liczenie nie jest takie proste i wiąże się wywołaniem kilku funkcji i utworzeniem kilku następnych obiektów :>

ogólnie konstruowanie obiektu outside ma składać się:
-obliczenie argumentów dla konstruktora obiektu obiekt_wew (kolejne funkcje i obiekty)
-dopiero teraz wywołanie konstruktora obiekt_wew z tymi parametrami
-dalsze ustawianie utworzonego obiektu obiekt_wew

w javie z tego co wiem, nie ma takich problemów, wywołujesz konstruktor obiektu wewnętrznego gdzie ci pasuje, a w C++ jest lista inicjalizacyjna, która wykonuje się przed działaniem konstruktora (w którym potrzebuję obliczyć parametry dla konstruktora wywoływanego w tej liście)

możliwe niezbyt eleganckie rozwiązania, na które mam koncepcję:

  1. obiekt_wew jako wskaźnik,
    wtedy wywołanie konstruktora(+alokacja obiektu), może nastąpić gdzieś w środku konstruktora outside, ale tworzenie go na stercie nie jest dobrym wyjściem, lepiej żeby był statycznie wsadzony w klasę outside(już wolę złamać wszelkie zasady OPP :P)

  2. łamanie wszelkich zasad OPP - sposób bardzo C, anty C++ :>
    niewywoływanie składniowo w ogóle konstruktora obiektu_wew(tzn. wtedy konstruktor domyślny którego bym musiał upublicznić), a za to, zrobić copy+paste kod konstruktora obiekt_wew do konstruktora outside, w miejsce gdzie powinien być (gdzie bym chciał, żeby był) wywoływany konstruktor obiekt_wew i tak zmienić ten wklejony kod (ten niby konstruktor obiekt_wew) żeby memset'ował prywatne składowe obiekt_wew(zainicjował je, tak jak konstruktor powinien) kompletnie olewając hermetyzację (wiem straszne xd), żeby maszynowo to działało tak jakby się w środku konstruktora outside wykonywał konstruktor obiektu obiekt_wew

  3. unknown name (taki, że ujdzie nie łamiąc za specjalnie zasad OPP, na ten bym się najbardziej zdecydował z tych trzech)
    otóż robię sobie metodę prywatną klasy outside, którą wywołam jako parametr konstruktora w liście inicjalizacyjnej

class outside { [...] protected: obliczParametry(arg z konstruktora outside) };
[...]
//i teraz konstruktora outside by wyglądał tak:
outside::outside(...args...): obiekt_wew(obliczParametry(...args...))
{
  //ustawianie 
}

obliczParametry, pewnie by zwracało jakąś strukturkę odpowiadającą parametrom, co by się wiązało ze zrobieniem kolejnej wersji konstruktora obiekt_wew z parametrem takiej strukturki(ale to nie problem), w obliczParametry bym obliczał potrzebne parametry dla konstruktora obiekt_wew, obiekt by się tworzył w liście, dalej bym sobie go ustawiał odpowiednio, niby wszystko pięknie, ale jest jedno ale: w obliczParametry musiałbym tworzyć 2 obiekty, które są potrzebne do obliczeń i do ustawiania obiektu, mógłbym je tworzyć w obliczParametry, a usuwać w konstruktorze outside, albo oddzielnie tworzyć te same obiekty dla zasięgu obliczParametry i konstruktora outside, tylko po co 2x to samo, a opcja z tworzeniem obiektu wymusza przekazania z obliczParametry do konstruktora outside pointery do utworzonych obiektów, albo je globalnie(globalnie odpada bo chcę zrobić wszystko w headerach jako inline), albo jako składowe klasy, tak żeby były widoczne tu i tu, tylko znowu trzeba robić składowe, które będą przez całe, życie obiektu outside, a są potrzebne tylko podczas tworzenia obiektu

najlepiej jakbym się dowiedział, że można jakoś w cpp określać moment wywołania konstruktora obiektu wewnętrznego o czym nie wiem, jeśli się nie da, trzeba będzie wybrać jakiś kompromis

0

jesli chcesz sie trzymac RAII - niestety, pozostaje Ci model #3..

a tak ogolnie, to Twoj przypadek bardziej mi podpada pod Two-Phase Construction niz pod 'czyste' RAII, one zreszta ze soba nie koliduja az tak..

2-PC to nic innego jak para metod:

  • konstruktor, domyslny, bez parametrowy, nie robiacy nic (moze poza zerowaniem/ustawieniem flagi kontrolnej bool notInitialized o ile jak w ogole chcesz miec)
  • metoda o nazwie a'la "Initialize" - pobierajaca parametry, wykonujaca wszystko to co trzeba
    [destruktor - normalnie, nie ma u Ciebie potrzeby 2-PDestruction]

ot, taki banal..

natoooomiassst, mozesz wywolac konstruktor po utworzeniu obiektu.
przeanalizuj to:

class Outside
{
whatever:
    Base obiekt_wew;
};

class Base
{
private:
    Base(){}; //nicnierobiacy! jak w 2-PC
public:
    Base(param1, param2, param..) { .... }
    friend class Outside; //niech Outside ma explicite dostep do Base::Base()
}

Outside::Outside(): obiekt_wew()  // default ctor
{
     blah param1 = new ...
     blah param2 = new ...
     blah param 3 = f(g(h(param1)) + z(param2));

     //and the winner is...
     new (&obiekt_wew) Base(param1, param2, param3..);  // <-this. in-place construction, constructs the object at *that* point of memory.
}

sorrry, ze po angielsku :}

  1. Base() niech naprawde nic nie robi. ono i tak bedzie wywolane, bo cos musi. ale szkoda zeby sie meczylo na marne, skoro zaraz potem je cos nadpisze.. dlatego tez jest prywatne - niedostpene dla nietwajemniczonych
  2. new(void*) Base(..params..) nie wywola destruktora na starym obiekcie ani nic takiego, ono NADPISZE tamto miejsce pamieci nowym obiektem. ot, zadnej magii..
  3. wlasciwie, new(void*) T() jest prezznaczone do rzadkich przypadkow kiedy sam chcialbys rządzic alokacja obiektow np. w wyznacoznym przez Ciebie kawalku pamieci (np. tab 500kB bajtow jako ograniczona pula pamieci na Twoje smieci), ale co tam:)
  4. nie pamietam, czy istnieje domyslne new()T(). jesli nie, to w Base dopisz przeciazony operator new, ktory nic nie robi, nic nie alokuje, tylko zwraca podany mu adres:
 operator new(size_t size, void*ptr){return ptr;}
0

Nie rozumiem co masz przeciwko rozwiązaniu 1. Jest najlepsze, najbardziej czytelne i zupełnie analogiczne do tego jak to robi Java.
Jeśli jednak się upierasz i są to twoje klasy to zrób konstrukcję dwu fazową - jest to też często stosowane rozwiązanie.
Jeśli klasa wewnętrzna pochodzi z innej biblioteki, to poza numerem 1 i 3 można jeszcze:
#4. przed ten feralny obiekt wstawić inny własny obiekt, który w swoim konstruktorze wykona potrzebne obliczenia ich wynik zostanie przekazany do konstruktora obiektu wewnętrznego. (może być to nawet klasa zdefiniowana wewnątrz klasy base i nie posiadająca, żadnych pól).
#5 rozbić klasę base na dwie klasy dziedziczące po sobie. W base1 wykonujesz potrzebne obliczania i przygotowujesz potrzebne obiekty, a ten feralny obiekt wewnętrzny tworzony będzie w klasie pochodnej.

Wszystko rozchodzi się o to by pamiętać jaka jest kolejność wywoływania konstruktorów.

0

hmm... case quetzalcoatl'a wydaje się być tym czego szukałem... nawet nie wiedziałem, że new ma taką możliwość nadpisania istniejącego obiektu(z wywołaniem konstruktora), no to by było to o co mi chodzi, jakbym w środku konstruktora outside mógł ponownie wywołać konstruktor dla obiektu_wew...
no to w sumie jest case #2, tyle, że zamiast brzydkich memset'ów, new ładnie z uwzględnieniem konstruktora i hermetyzacji w obiekt_wew nadpisuje pamięć :>

jeśli dobrze rozumiem to
#4, to będzie praktycznie to samo, co #3, tyle, że zamiast metody obliczParametry w liście idzie konstruktor tej dodatkowej pustej klasy, ale problemy pozostają te same, bo co z tego, że wywołam konstruktor tej pustej klasy, jak on mi nic nie może zwrócić i obiekty powstałe i wyniki obliczeń w tym konstruktorze i tak trzeba przekazać zewnętrzną drogą do konstruktora outside, przed dodatkowe składowe w outside? których nie chcę właśnie robić, bo s tylko potrzebne na czas konstruowania outside, sytuacja jak w #3

no nic jutro przysiądę do kodu, zobaczymy, jakby coś nie szło dam znać :>

//EDIT
case quetzalcoatl'a śmiga, wielkie dzięki
Temat do zamknięcia

0

Wszystko zależy od szczegółów. Jakbyś pokazał swój kod, a nie jakieś uogólnienie, to na pewno ktoś ci poda eleganckie rozwiązanie.

0

coz.. ja tez sie zgadzam, ze zwykle najlepszy jest po po prostu ptr plus 2-phase construction.. ale sa przypadki ze nie mozna sobie na to pozwolic - np. obiekt musi byc POD i zero wskaznikow w srodku, albo jest tak krytyczny czasowo ze i jeden level indirection zrobi duzo szumu.. IMHO korzystanie z new-inplace to juz ostatecznosc

0

w moim przypadku nie chciałem robić wskaźnika, bo to było jedno z wąskich gardeł, które będzie się wiele razy wykonywać(jak 2 czy 5 razy, to by mi nie zależało, ale znacznie znacznie więcej), tzn. będzie mnóstwo odwołań do base przez outside, przekopywanie się przez wskaźnik byłoby niepotrzebnym zabiegiem

gorsze jest to, że istnienie tego wskaźnika byłoby wymuszone tylko i wyłącznie tym, że obiektowy zapis nie pozwala na statyczne szybsze użycie takiego obiektu, a tego przecież nie chcemy, ażeby obiektowość powodowała wolniejszą pracę programu, na szczęście jak się okazało można tak zrobić i obiektowo, ażeby było możliwie najszybciej

btw. przykład już nieaktualny, bo postanowiłem inaczej skonstruować tę część kodu, w którym tu nie ma już takiego obiektu base :> aczkolwiek obejścia tego problemu może przydać się na przyszłość :>

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