/*!
 * Autor: Marcin Kaczor U3ISI nr albumu: 192641
 * Temat: Obsluga braku pamieci (praca domowa na ZPR)
 *
 * Problem: Podczas korzystania z dynamicznego przydzialu pamieci w jezyku C++
 * (za pomoca operatora new) jesli nie uda sie zarezerwowac bloku pamieci
 * o odpowiednim rozmiarze, to zostanie rzucony wyjatek std::bad_alloc.
 * Sprawdzanie czy po każdym wywolaniu new nie zostal rzucony wyjatek jest
 * klopotliwe. Oczywiscie mozna uzywac wersji operatora new, ktora nie rzuca
 * wyjatkiem new (std::nothrow) Typ, ale wtedy pomijamy obsługę błędów.
 *
 * Opis: Bilbioteka standardowa udostepnia mozliwosc zdefiniowania wlasnej funkcji
 * obslugi braku pamieci za pomoca funkcji z biblioteki standardowej set_new_handler().
 * Funkcja obslugi braku pamieci powinna uzyskac wiecej pamieci, rzucic wyjatek,
 * przerwac program lub chociaz ustawic inna funkcje obslugi (lub odinstalowac
 * obecna), bo program bedzie sie wykonywal w nieskonczonej petli.
 *
 * Zastosowanie: Glownym zadaniem funkcji obslugi braku pamieci jest uzyskanie 
 * wiekszej ilosci pamieci (np. przez usuniecie nieuzywanych obiektow) tak aby
 * aplikacja mogla dzialac dalej, czasami jest to niemozliwe, funkcja ta jest tez
 * przydatna gdy nie chcemy przy kazdym tworzeniu nowego obiektu obslugiwac
 * wyjatku bad_alloc, w ten sposob, gdy juz wystapi problem braku pamieci mozemy
 * to obsluzyc w jednym miejscu i zamknac aplikacje w mniej brutalny sposob, mamy
 * szanse na pozamykanie polaczen, plikow oraz poinformowanie uzytkownika o tym co
 * sie stalo.
 * 
 * Przyklad: Nizej zostanie przedstawiony przyklad klasy reprezentujacej obiekty
 * zajmujace duzo pamieci, obiekty beda rejestrowane w rejstrze przez ktory tez
 * jest mozliwy do nich dostep, rejestr bedzie usuwac obiekty gdy bedzie
 * potrzebna pamiec.
 */

#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
/*!
  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
/*!
  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 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 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
}


int main ()
{
  std::set_new_handler(CleanUpMemory); // ustawienie nowej funkcji obslugi braku pamieci
  for(;;) {
    Register::getInstance().getNewBigSize(); // zajmowanie pamieci :-)
  }
  return 0;
}
