Unie i konstruktory

2007-09-18 19:17

Przychodzi czasem ochota, aby zapewnić dostęp do tych samych danych na różne sposoby. Ten pomysł można zrealizować w sposób poprawny albo i nie :) Dzisiaj właśnie przekonałem się ostatecznie, dlaczego stosowana przeze mnie metoda należała do tej drugiej kategorii.

Otóż dość powszechnie wykorzystuję pewną strukturę o nazwie SMouseEventArgs, która docelowego zawiera informacje o jakimś zdarzeniu związanym z myszą (klik, przesunięcie, itd.). Wśród tych informacji jest między innymi pozycja kursora w momencie zajścia zdarzenia, która początkowo była opisana po prostu przez dwa pola X i Y.

W pewnym momencie zauważyłem jednak, że dość często stosuję konstrukcję podobną do tej:

  1. SMouseEventArgs mea(/*...*/);
  2. // ...
  3. SomeMethod (POINT2F(mea.X, mea.Y));

gdzie POINT2F jest strukturą opisującą punkt na płaszczyźnie. Aby zaoszczędzić sobie pisania (i konstrukcji tymczasowego obiektu) postanowiłem w przypływie kreatywności dodać do SMouseEventArgs drugi sposób dostępu do tych współrzędnych – pole Position. Jak? Otóż… przy pomocy unii:

  1. struct SMouseEventArgs
  2. {
  3.    union
  4.    {
  5.       struct { float X, Y; };
  6.       POINT2F Position;
  7.    };
  8.  
  9.    // ...
  10. };

Sęk w tym, że POINT2F jest porządną strukturą i zawiera między innymi kilka konstruktorów. A jest niedozwolone, by obiekt klasy posiadającej zdefiniowany przez programistę konstruktor albo nietrywialny destruktor mógł być elementem unii.
Kompilator raczył mnie więc błędem, ale wówczas w kolejnym przypływie kreatywności stosowałem “objeście” w postaci anonimowej struktury:

  1. struct { POINT2F Position; };

I byłem święcie przekonany, że przecież skoro POINT2F nie alokuje własnej pamięci, nie otwiera plików ani nie robi żadnych innych tego typu czynności, po których trzeba by sprzątać, to przecież nic złego nie może się stać…

Naturalnie byłem w błędzie :) Dalszym elementem układanki jest konstruktor SMouseEventArgs, przyjmujący kilka parametrów i inicjalizujący nimi strukturę:

  1. SMouseEventArgs(float x, float, y /*... */) : X(x), Y(y) { }

Na oko niby wszystko jest w porządku. Tylko dlaczego cokolwiek byśmy przekazali jako x i y, w wynikowej strukturze zawsze i tak współrzędne będą zerami?!
Ot, kolejna nierozwiązania zagadka Wszechświata. Przynajmniej do chwili, gdy uświadomimy sobie dwie rzeczy:

  • konstruktor zawsze inicjalizuje wszystkie pola klasy – nie tylko te na liście inicjalizacyjnej
  • kolejność inicjalizacji tych pól jest taka sama jak kolejność ich deklaracji w klasie

Aplikując te zasady do powyższego przypadku, mamy bardzo ciekawy scenariusz. Mianowicie pola X i Y są poprawnie wypełniane parametrami konstruktora SMouseEventArgs, lecz w chwilę potem to samo miejsce pamięci jest… nadpisywane przez konstruktor POINT2F. Dlaczego? Ano dlatego, że pole Position też musi zostać zainicjowane, a domyślny konstruktor POINT2F wstępnie ustawia je na punkt (0,0).

Mniej więcej takie są skutki prób bycia mądrzejszym od twórców języka i kompilatora :) Można by oczywiście brnąć dalej w to rozwiązanie, zmieniając kolejność deklaracji pól albo jawnie inicjalizować pole Position zamiast X i Y. Cały czas jednak jest to stąpanie po cienkim lodzie.
Dlatego chyba najwyższy czas ograniczyć swoją kreatywność i następnym razem zastosować może mało efektowne, ale za to stuprocentowo bezpieczne metody dostępowe :)

Tags: , ,
Author: Xion, posted under Programming »


3 comments for post “Unie i konstruktory”.
  1. Reg:
    September 23rd, 2007 o 9:41

    Pierwszy raz widzę, żeby ktoś kazał domyślnemu konstruktorowi struktury typu wektor inicjalizować pola zerami. To jest 1. strata czasu i wydajności 2. prowadzi do takich błędów jakie tu własnie opisałeś. Jeśli taka prosta struktura ma się zachowywać niemal jak typ wbudowany to wzorem typów int czy float, kiedy nie jest jawnie ustawiona, powinna pozostać niezainicjalizowana.

  2. Xion:
    September 23rd, 2007 o 9:47

    To jest robione automatycznie. Domyślny konstruktor struktury POINT2F to po prostu:

    POINT2F() { }

    ale jak wiadomo, przed jego wykonaniem pola struktury (czyli tutaj x i y) same się inicjalizują ‘domyślnie’. Teoretycznie dla typów wbudowanych powinno to oznaczać, że ich pamięć jest w ogóle nietknięta, ale najwyraźniej w trybie Debug jest zerowana. (Nie wiem, jak jest w trybie Release, ale ponieważ poprawiłem już tego babola, to nie chce mi się sprawdzać :)).

Comments are disabled.
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.