Problem: Podczas korzystania z dynamicznego przydziału pamięci w języku C++ (za pomocą operatora new) jeśli nie uda się zarezerwować bloku pamięci o odpowiednim rozmiarze, to zostanie rzucony wyjątek std::bad_alloc. Sprawdzanie czy po każdym wywołaniu new nie został rzucony wyjątek jest kłopotliwe. Oczywiście można używac wersji operatora new, która nie rzuca wyjątkiem new (std::nothrow) Typ, ale wtedy pomijamy obsługę błędów.
Opis: Biblioteka standardowa udostępnia możliwość zdefiniowania własnej funkcji obsługi braku pamięci za pomocą funkcji z biblioteki standardowej set_new_handler(). Funkcja obsługi braku pamięci powinna uzyskać więcej pamięci, rzucić wyjątek, przerwać program lub chociaż ustawić inna funkcje obsługi (lub odinstalować obecna), bo program będzie się wykonywał w nieskończonej pętli.
Zastosowanie: Głównym zadaniem funkcji obsługi braku pamięci jest uzyskanie większej ilości pamięci (np. przez usuniecie nieużywanych obiektów) tak aby aplikacja mogla działać dalej, czasami jest to niemożliwe, funkcja ta jest tez przydatna gdy nie chcemy przy każdym tworzeniu nowego obiektu obsługiwać wyjątku bad_alloc, w ten sposób, gdy już wystąpi problem braku pamięci możemy to obsłużyć w jednym miejscu i zamknąc aplikacje w mniej brutalny sposób, mamy szanse na pozamykanie połączeń, plików oraz poinformowanie użytkownika o tym co się stało.
Przyklad: Niżej zostanie przedstawiony przykład klasy reprezentującej obiekty zajmujące dużo pamięci, obiekty będą rejestrowane w rejestrze przez który tez jest możliwy do nich dostęp, rejestr będzie usuwać obiekty gdy będzie potrzebna pamięć.
Kod: Dołączenie nagłówków, deklaracje…
#include <new> // zawiera funkcje set_new_handler #include <iostream> // obiekty cout, cerr, etc. #include <cstdlib> // exit() - awayjne wyjscie #include <vector> // wektor obietktow BigSize //! wielkosc bufora powiekszajacego rozmiar BigSize #define BS_SIZE 200000000 // deklaracja klasy Register potrzebna klasie BigSize class Register;
Klasa BigSize reprezentuje duże obiekty, które alokujemy dynamicznie.
//! Klasa BigSize /*! Klasa BigSize reprezentuje duze obiekty w pamieci, np. warstwy w zaawansowanym programie do obrobki grafiki rastrowej lub zapamietywanie histori zmian w obrazie. */ class BigSize { friend class Register; private: //! Prywatny konstruktor, tylko obiekty klasy Register maja prawo tworzyc obiekty //! BigSize. Dla celow testowych informuje uzytkownika o stworzeniu obiektu BigSize() { std::cout << "Creating BigSize..." << std::endl; } //! Prywatny konstruktor kopiujacy, zabezpiecza przed kopiowaniem obiektow, //! dostep do obiektu jest mozliwy tylko przez klase register BigSize(const BigSize &) {}; //! Prywatny destruktor, tylko obiekty klasy Register maja prawo niszczyc obiekty //! BigSize. Dla celow testowych informuje uzytkownika o usunieciu obiektu ~BigSize() { std::cout << "Deleting BigSize..." << std::endl; } private: //! Tablica znakow, bufor, w przykladzie tylko sztucznie powieksza rozmiar klasy char buffer_[BS_SIZE]; };
Klasa Register zarządzająca obiektami klasy BigSize. W tym przykładzie gdy zostanie o to poproszona usuwa wszystkie obiekty klasy BigSize, ale można ją urozmaicić o licznik odwołań lub czas ostatniego odwołania do poszczególnych obiektów i usuwać ostatnio nieużywane obiekty lub rzadko używane.
//! Klasa Register /*! Klasa Register tworzy, usuwa i przechowyje wskazania na obiekty BigSize, zaimplemneotwna jako Singleton, mozna przy implemntacji wykorzystac rowniez wzorzec fabryki */ class Register { private: //! Prywatny konstrukotr domyslny i kopiujacy Register() : counter_(0) {}; Register(const Register &r); public: //! Metoda pobierajaca referencje do obiektu static Register& getInstance() { static Register instance; return instance; } //! Tworzenie nowego obiektu klasy BigSize BigSize *getNewBigSize() { BigSize *bs = new BigSize(); objects_.push_back(bs); counter_++; return bs; } //! zwolnienie nieuzywanych obiektow, w tym przykladzie wszystkie obiekty sa nieuzywane //! metoda zwaraca true zostala zwolniona jakakolwiek ilosc pamieci, w przeciwnym razie false bool deleteUnusedObjects() { bool retval = counter_ > 0 ? true : false; while (counter_--) { delete objects_.back(); objects_.pop_back(); } counter_ = 0; return retval; } //! Pobranie obiektu klasy BigSize o zadanym indeksie, niewykorzystywane w przykladzie BigSize *getBigSize(int index) { if (index > 0 && index < counter_) return objects_[index]; else return NULL; } private: int counter_; /**< licznik obiektow */ std::vector<BigSize *> objects_; /**< wektor obiektow */ };
Funkcja obsługi braku pamięci, w tym przykładzie nieużywana, ale pokazuje, że można mieć więcje niż jedną funkcję obsługi braku pamięci i mogą mieć one różne zadania, ta stara się logować powód zamknięcia aplikacji i wychodzi z aplikacji z błędem.
//! Funkcja obslugi braku pamieci, zamyka awaryjnie aplikacje void KillApplication() { // informacja dla uzytkownika, w zaleznosci od potrzeb moze byc zapisana do logu std::cerr << "Pamiec zostala wyczerpana, awaryjne zamykanie aplikacji." << std::endl; // awaryjne zamykanie aplikacji, zapisanie stanu, zamkniecie plikow, polaczen, etc. exit(1); }
Funkcja obsługi braku pamięci, zajmuje się zwalnianiem nieużywanej pamięci.
//! Funkcja obslugi braku pamieci, sprzata pamiec void CleanUpMemory() { // informacja dla uzytkownika, potrzebna raczej tylko dla aplikacji testowej std::cout << "Pamiec zostala wyczerpana, usuwanie niepotrzebnych obiektow." << std::endl; if (!Register::getInstance().deleteUnusedObjects()) { // nie udalo sie zwolnic pamieci std::set_new_handler(KillApplication); // ustawienie nowej funkcji obslugi braku pamieci return; } exit(1); // program moze kontynuowac dzialanie, bo zwolniono czesc nieuzywanej pamieci, // ale w tym przykladzie dzialalby w petli nieskonczonej, dlatego zostanie zamkniety }
Funkcja main, ustawia nową funkcję obsługi braku pamięci i zajmuje pamięć.
int main () { std::set_new_handler(CleanUpMemory); // ustawienie nowej funkcji obslugi braku pamieci for(;;) { Register::getInstance().getNewBigSize(); // zajmowanie pamieci :-) } return 0; }