Posts tagged ‘new’

Cztery rodzaje operatora new

2008-05-20 21:04

Do alokowania pamięci (albo raczej: tworzenia obiektów na stercie) służy w C++ operator o wiele mówiącej nazwie new. Chociaż jest on powszechnie znany i nieustannie używany przez każdego programistę C++, pewni nie wszyscy wiedzą, że występuje on nie w jednej, ale aż w czterech odmianach, z których każda różni się sposobem wywołania!
Oto krótki przegląd:

  • “Zwykły” operator new (co obejmuje też formę tablicową new[]) jest oczywiście najbardziej znany i najczęściej stosowany. Warto pamiętać, że zazwyczaj robi on dwie rzeczy: oprócz alokacji pamięci wywołuje też konstruktor dla tworzonego obiektu (lub obiektów w tablicy), któremu możemy też podać parametry.
  • Przeciążony operator new zmienia natomiast swoje zachowanie tylko w zakresie tej pierwszej czynności, czyli samej alokacji. Ciekawostką jest to, że możemy wyposażyć go (tj. sam operator new) w dodatkowe parametry – czyli przeciążyć go w pełnym znaczeniu tego słowa! Typowym przykładem, jaki się tutaj zwykle przytacza, jest następująca funkcja:
    1. void* operator new (size_t bytes, char fill)
    2. {
    3.     // alokacja z wypełnieniem podanym wzorcem
    4.     char* p = ::new char[bytes];
    5.     for (size_t i = 0; i < bytes; ++i) p&#91;i] = fill;
    6.     return p;
    7. }&#91;/cpp]
    8. która tworzy przeciążoną wersję operatora <code>new</code>, wypełniającą świeżo zaalokowaną pamięć podanym wzorcem:
    9. [cpp]char* ptr = new('X') char[10];  // alokuje tablicę charów wypełnioną X-ami

    Naturalnie możliwe są bardziej przydatne zastosowania. Jeśli mamy na przykład kilka rozłącznych ze sobą stert, to możemy tak napisać operator new, by poprzez dodatkowy argument pozwalał decydować o tym, którą z nich chcemy w danym przypadku wybrać.

  • new nierzucający wyjątków. Domyślnie alokacja za pomocą new rzuca wyjątek std::badalloc, jeżeli operacja się nie powiodła (zwykle z powodu braku pamięci). To zachowanie – wymagające do poprawnej obsługi bloku try-catch może nam się nie podobać, ale na szczęście można je zmienić. Wystarczy użyć wersji new z dodatkowym parametrem std::nothrow:
    1. int* pEnormousArray = new(std::nothrow) int[0xffffffff]; // "tylko" 4GB :)

    Wymaga to jeszcze dołączenia standardowego pliku nagłówkowego o wielce trafnej nazwie new.

  • Ostatni rodzaj zwie się placement new, co nie ma żadnego specjalnie dobrego tłumaczenia na język polski. Użycie tego operatora wymaga podania wskaźnika na już zaalokowany kawałek pamięci. Działanie operatora new ogranicza się wtedy do skonstruowania obiektu w tym właśnie miejscu, na które pokazuje przekazany wskaźnik. Tak więc w tym przypadku new tak naprawdę niczego nie alokuje; jest to po prostu najzupełniej legalny sposób na wywołanie konstruktora bez robienia czegokolwiek innego. Jakkolwiek może to się wydawać przydatne, zdecydowanie odradzam korzystania z tego mechanizmu w sposób nieprzemyślany, bo można przy tym popełnić “ciekawe” błędy.

Mamy więc aż cztery różne warianty new, ale raczej nie powinno to rodzić dylematów w rodzaju “Który z nich wybrać?”. W praktyce i tak nieczęsto zachodzi potrzeba skorzystania z któregokolwiek poza pierwszym. Co nie znaczy rzecz jasna, że nie warto znać pozostałych – podobnie jak całej masy innych kruczków języka C++ :]

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

Obiekty są pamiętliwe

2007-07-24 14:47

Czasami mylne wyobrażenie o pewnych elementach języka programowania potrafi ściągnąć na nas nie lada kłopoty. Właśnie ostatnio mogłem o się o tym przekonać, a sprawa dotyczy niezbyt często wykorzystywanego elementu C++, a mianowicie placement new.

Cóż to takiego? Jest to sposób na podanie operatorowi new adresu miejsca w pamięci, który ma wykorzystać. Może to brzmieć niedorzecznie, bo przecież new ma za zadanie właśnie alokację pamięci. To jednak tylko część prawdy, bowiem ten operator wywołuje też konstruktor tworzonego obiektu. W przypadku wersji placement będzie to więc jedyna czynność, jaką wykona.
Składnia placement new wygląda mniej więcej tak:

  1. CFoo* = new (pvAddress) CFoo(...);

Na czym jednak polega problem? Otóż tego specjalnego wariantu operatora new nie można wywoływać bezkarnie. W szczególności nie jest to sposób na “przezroczyste” wywołanie konstruktora dla jakiegoś obiektu.

Pytanie oczywiście brzmi: do czego było mi potrzebne użycie tego rzadkiego mechanizmu języka? No cóż, teraz już wiem, że do niczego, jednak wcześniej myślałem, że będzie to niezły sposób na wczytywanie zasobów.
W skrócie: w mojej bibliotece zasoby takie jak np. tekstury czy bufory tworzą dość prostą hierarchię dziedziczenia. Parametry niektórych z nich – jak np. tekstur – można wczytywać z pliku tekstowego – w tym przypadku chodzi chociażby o nazwę pliku graficznego z obrazkiem tekstury.
Sęk w tym, że zasób tekstury (reprezentowany przez klasę CTexture) jest też zasobem DirectX (dziedziczy po IDxResource). A z tym związane są kolejne parametry, jak np. pula pamięci czy sposób użycia zasobu (to akurat nie jest mój wymysł, tylko DirectXa ;P). One również mogą być zapisane w pliku i trzeba je uwzględnić przy wczytywaniu tekstury.

W moim “genialnym” rozwiązaniu wymyśliłem więc, że podczas tworzenia obiektu CTexture zostanie najpierw stworzony obiekt IDxResource (dzięki czemu zostanie załatwiona kwestia “DX-owych” parametrów). Następnie w tym samym miejscu pamięci – placement new! -skonstruujemy obiekt CTexture, który zajmie się załadowaniem pozostałych danych.
I to rzeczywiście działało, dopóki się nie zorientowałem, że zapomniałem dopisać do menedżera zasobów kodu, który zwalniałby wszystko przy kończeniu programu. Wtedy to w ruch poszły destruktory, a wtedy pojawiły się… przerwania systemowe informujące o uszkodzeniu sterty.

Powodem jest fakt, że obiekty (a ściślej mechanizm ich alokacji) pamiętają sposób, w jaki zostały stworzone. Jeżeli było to zwykłe new, to zniszczenie obiektu pociąga za sobą zwolnienie pamięci. Lecz jeżeli było to placement new, to operator delete zakłada, że nic nie wie o obszarze pamięci, w którym obiekt rezyduje – więc go nie zwalnia. W istocie FAQ C++ wyraźnie pisze, by przy używaniu placement new samemu wywoływać destruktor, a potem samodzielnie zwalniać pamięć.
placement new nie jest po prostu sposobem na wywołanie konstruktora – z jego użyciem wiążą się określone konsekwencje. Jak widać czasami można przekonać się o nich w mało przyjemny sposób :)

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


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