3rd March 2009, 05:38 pm
Jak się okazuje nic nie jest na początku idealne. Dzięki uwagom Revo udało mi się usprawnić szablon automatycznych uchwytów. Revo poradził mi dodanie dodatkowego wskaźnika na licznik odwołań do danego zasobu. Dzięki temu możliwe było usunięcie jednej funkcji wirtualnej, a druga została sprowadzona do roli sprzątaczki, która zostaje wywołana tylko w momencie, gdy licznik osiąga wartość 0. Dodatkowo dodałem sprawdzenie czy uchwyt nie jest zerowy przed odwołaniem się do wskaźników.
Poniżej znajduję się kod poprawionej klasy szablonowej AutomaticHandle:
typedef unsigned int Dword;
template< typename TAG>
class AutomaticHandle
{
public:
// Podstawowy konstruktor
AutomaticHandle(NLib::Dword handle, MHandleMgr* manager, NLib::Dword* resCounter) : m_handle(handle), m_manager(manager), m_resCounter(resCounter)
{ if(m_handle) ++(*m_resCounter); }
// Konstruktor kopiujący
AutomaticHandle(const AutomaticHandle& src) : m_manager(src.m_manager), m_handle(src.m_handle), m_resCounter(src.m_resCounter)
{ if(m_handle) ++(*m_resCounter); }
// Destruktor
~AutomaticHandle()
{
if(m_handle)
{ if(!(--(*m_resCounter))) m_manager->ReleaseResource(m_handle); }
}
// Operator przypisania
const AutomaticHandle& operator =(const AutomaticHandle& src)
{
if(m_handle)
{ if(!(--(*m_resCounter))) m_manager->ReleaseResource(m_handle); }
m_handle = src.m_handle;
if(m_handle) ++(*(m_resCounter = src.m_resCounter));
return src;
}
// Operator równości
bool operator ==(const AutomaticHandle& src)
{ return m_handle == src.m_handle; }
// Operator różności
bool operator !=(const AutomaticHandle& src)
{ return m_handle != src.m_handle; }
// Operator Dword
operator NLib::Dword() const
{ return m_handle; }
// Operator bool
operator bool() const
{ return !!m_handle; }
private:
// Uchwyt
NLib::Dword m_handle;
// Wskaźnik do managera
MHandleMgr* m_manager;
// Wskaźnik do licznika zasobu
NLib::Dword* m_resCounter;
}; |
typedef unsigned int Dword;
template< typename TAG>
class AutomaticHandle
{
public:
// Podstawowy konstruktor
AutomaticHandle(NLib::Dword handle, MHandleMgr* manager, NLib::Dword* resCounter) : m_handle(handle), m_manager(manager), m_resCounter(resCounter)
{ if(m_handle) ++(*m_resCounter); }
// Konstruktor kopiujący
AutomaticHandle(const AutomaticHandle& src) : m_manager(src.m_manager), m_handle(src.m_handle), m_resCounter(src.m_resCounter)
{ if(m_handle) ++(*m_resCounter); }
// Destruktor
~AutomaticHandle()
{
if(m_handle)
{ if(!(--(*m_resCounter))) m_manager->ReleaseResource(m_handle); }
}
// Operator przypisania
const AutomaticHandle& operator =(const AutomaticHandle& src)
{
if(m_handle)
{ if(!(--(*m_resCounter))) m_manager->ReleaseResource(m_handle); }
m_handle = src.m_handle;
if(m_handle) ++(*(m_resCounter = src.m_resCounter));
return src;
}
// Operator równości
bool operator ==(const AutomaticHandle& src)
{ return m_handle == src.m_handle; }
// Operator różności
bool operator !=(const AutomaticHandle& src)
{ return m_handle != src.m_handle; }
// Operator Dword
operator NLib::Dword() const
{ return m_handle; }
// Operator bool
operator bool() const
{ return !!m_handle; }
private:
// Uchwyt
NLib::Dword m_handle;
// Wskaźnik do managera
MHandleMgr* m_manager;
// Wskaźnik do licznika zasobu
NLib::Dword* m_resCounter;
};
Kolejną klasą jest AutoHandleMgr, czyli klasa implementująca obsługę automatycznych uchwytów. Aktualnie tylko trzy funkcje są dostępne klasie pochodnej, w tym jedna dla uchwytów. Klasa ta posiada jedną funkcję abstrakcyjną, w której po zdefiniowaniu w klasie pochodnej należy usuwać odpowiednie zasoby zgodnie z przekazanym indeksem.
Oto jej definicja:
typedef unsigned int Dword;
class AutoHandleMgr
{
// Unia do wyciągania informacji z uchwytu
enum
{
// Rozmiary pól
MAX_BITS_INDEX = sizeof(NLib::Dword) * 4,
MAX_BITS_MAGIC = sizeof(NLib::Dword) * 4,
// Maksymalne wartości pól
MAX_INDEX = (1 << MAX_BITS_INDEX) - 1,
MAX_MAGIC = (1 << MAX_BITS_MAGIC) - 1
};
public:
// Funkcja do zwracania uchwytu
void ReleaseResource(NLib::Dword handle)
{
NAssert(GetIndex(handle) < m_magicValues.size(), "Wrong Handle");
NAssert(CheckMagic(handle), "Wrong handle");
DeleteHandleAt(GetIndex(handle));
ReleaseResourceByIndex(GetIndex(handle));
}
protected:
// Funkcja tworzy nowy uchwyt i go zwraca
// Index zawarty w uchwycie odpowiada indeksowi w tablicy z danymi
// w odziedziczonej klasie
NLib::Dword CreateNewHandle()
{
NLib::Dword handle;
static NLib::Dword s_autoMagic = 0;
if(++s_autoMagic > MAX_MAGIC) s_autoMagic = 1; // 0 oznacza pusty uchwyt
// Jeżeli nie ma wolnych miejsc to tworze nowy index
if(m_freeSlots.empty())
{
handle = m_magicValues.size() << (MAX_BITS_INDEX - 1);
m_magicValues.push_back(s_autoMagic);
}
else
{
handle = m_freeSlots.back() << (MAX_BITS_INDEX - 1);
m_magicValues[m_freeSlots.back()] = s_autoMagic;
m_freeSlots.pop_back();
}
handle |= s_autoMagic;
return handle;
}
// Funkcja zwraca index
NLib::Dword GetIndex(NLib::Dword handle)
{ return (handle >> (MAX_BITS_INDEX - 1)) & MAX_INDEX; }
private:
// Funkcja kasuje wskazany uchwyt
void DeleteHandleAt(NLib::Dword index)
{ m_freeSlots.push_back(index);
m_magicValues[index] = 0; }
// Funkcja zwraca część magiczną
NLib::Dword GetMagic(NLib::Dword handle)
{ return handle & MAX_MAGIC; }
// Funkcja sprawdzająca wartośc magic
bool CheckMagic(NLib::Dword handle)
{ NAssert(GetIndex(handle) < m_magicValues.size(), "Wrong Handle");
return GetMagic(handle) == m_magicValues[GetIndex(handle)]; }
private:
// Funkcja dekrementująca licznik w zasobach
virtual void ReleaseResourceByIndex(NLib::Dword index) = 0;
private:
// Vector zawierający magiczne wartości
std::vector m_magicValues;
// Vector zawierający indeksy z wolnymi miejscami
std::vector m_freeSlots;
}; |
typedef unsigned int Dword;
class AutoHandleMgr
{
// Unia do wyciągania informacji z uchwytu
enum
{
// Rozmiary pól
MAX_BITS_INDEX = sizeof(NLib::Dword) * 4,
MAX_BITS_MAGIC = sizeof(NLib::Dword) * 4,
// Maksymalne wartości pól
MAX_INDEX = (1 << MAX_BITS_INDEX) - 1,
MAX_MAGIC = (1 << MAX_BITS_MAGIC) - 1
};
public:
// Funkcja do zwracania uchwytu
void ReleaseResource(NLib::Dword handle)
{
NAssert(GetIndex(handle) < m_magicValues.size(), "Wrong Handle");
NAssert(CheckMagic(handle), "Wrong handle");
DeleteHandleAt(GetIndex(handle));
ReleaseResourceByIndex(GetIndex(handle));
}
protected:
// Funkcja tworzy nowy uchwyt i go zwraca
// Index zawarty w uchwycie odpowiada indeksowi w tablicy z danymi
// w odziedziczonej klasie
NLib::Dword CreateNewHandle()
{
NLib::Dword handle;
static NLib::Dword s_autoMagic = 0;
if(++s_autoMagic > MAX_MAGIC) s_autoMagic = 1; // 0 oznacza pusty uchwyt
// Jeżeli nie ma wolnych miejsc to tworze nowy index
if(m_freeSlots.empty())
{
handle = m_magicValues.size() << (MAX_BITS_INDEX - 1);
m_magicValues.push_back(s_autoMagic);
}
else
{
handle = m_freeSlots.back() << (MAX_BITS_INDEX - 1);
m_magicValues[m_freeSlots.back()] = s_autoMagic;
m_freeSlots.pop_back();
}
handle |= s_autoMagic;
return handle;
}
// Funkcja zwraca index
NLib::Dword GetIndex(NLib::Dword handle)
{ return (handle >> (MAX_BITS_INDEX - 1)) & MAX_INDEX; }
private:
// Funkcja kasuje wskazany uchwyt
void DeleteHandleAt(NLib::Dword index)
{ m_freeSlots.push_back(index);
m_magicValues[index] = 0; }
// Funkcja zwraca część magiczną
NLib::Dword GetMagic(NLib::Dword handle)
{ return handle & MAX_MAGIC; }
// Funkcja sprawdzająca wartośc magic
bool CheckMagic(NLib::Dword handle)
{ NAssert(GetIndex(handle) < m_magicValues.size(), "Wrong Handle");
return GetMagic(handle) == m_magicValues[GetIndex(handle)]; }
private:
// Funkcja dekrementująca licznik w zasobach
virtual void ReleaseResourceByIndex(NLib::Dword index) = 0;
private:
// Vector zawierający magiczne wartości
std::vector m_magicValues;
// Vector zawierający indeksy z wolnymi miejscami
std::vector m_freeSlots;
};
W przypadku tego menadżera uchwyty muszą być usunięte przed nim, inaczej pojawią się błędy z dostępem do pamięci.
28th February 2009, 02:19 am
Właśnie mija tydzień od zakończenia dwutygodniowego pobytu w domu w czasie końcówki sesji i przerwy egzaminacyjnej. W tym czasie rozpocząłem przepisywania mojego “silnika” od nowa, tym razem w oparciu o interfejsy. Są one bardzo poręczne, gdy chce się ukryć implementację klas wewnątrz statycznych, bądź dynamicznych bibliotek. Głównym założeniem przy projektowaniu drugiej wersji NIne było właśnie ukrycie implementacji wewnątrz statycznej biblioteki .lib. Dzięki takiemu postępowaniu biblioteka wygląda ładnie i pozwala ładnie rozszerzać własną funkcjonalność.
Drugim zagadnieniem poruszanym w tej notce są uchwyty, które same zarządzają licznikiem danego zasobu.
Z początku implementację uchwytów opierałem na artykule znajdującym się w książce “Game Programming Gems”, jednak problemem okazało się zmuszenie tychże uchwytów do sprzątania po sobie zasobów. Na szczęście rozwiązanie okazało się proste, ponieważ wystarczyło umieścić w klasie uchwytu wskaźnik do menadżera zasobów.
Poniżej znajduje się moja implementacja takiego uchwytu:
template< typename TAG>
class AutomaticHandle
{
public:
// Podstawowy konstruktor
AutomaticHandle(Dword handle, MHandleMgr* manager) : m_handle(handle), m_manager(manager)
{}
// Konstruktor kopiujący
AutomaticHandle(const AutomaticHandle& src) : m_manager(src.m_manager), m_handle(src.m_handle)
{ m_manager->Copy(src.m_handle); }
// Destruktor
~AutomaticHandle()
{ m_manager->Release(m_handle); }
// Operator przypisania
const AutomaticHandle& operator =(const ManagedHandle& src)
{ m_manager->Release(m_handle);
m_handle = src.m_handle;
m_manager->Copy(src.m_handle);
return src; }
// Operator Dword
operator Dword() const
{ return m_handle; }
/*
...
*/
private:
// Uchwyt
Dword m_handle;
// Wskaźnik do managera
MHandleMgr* m_manager;
}; |
template< typename TAG>
class AutomaticHandle
{
public:
// Podstawowy konstruktor
AutomaticHandle(Dword handle, MHandleMgr* manager) : m_handle(handle), m_manager(manager)
{}
// Konstruktor kopiujący
AutomaticHandle(const AutomaticHandle& src) : m_manager(src.m_manager), m_handle(src.m_handle)
{ m_manager->Copy(src.m_handle); }
// Destruktor
~AutomaticHandle()
{ m_manager->Release(m_handle); }
// Operator przypisania
const AutomaticHandle& operator =(const ManagedHandle& src)
{ m_manager->Release(m_handle);
m_handle = src.m_handle;
m_manager->Copy(src.m_handle);
return src; }
// Operator Dword
operator Dword() const
{ return m_handle; }
/*
...
*/
private:
// Uchwyt
Dword m_handle;
// Wskaźnik do managera
MHandleMgr* m_manager;
};
Jak widać klasa zawiera dodatkową zmienną – wskaźnik na klasę menadżera ustawiany przy konstrukcji. Klasa menadżera musi zawierać używane metody, które wewnątrz zarządzają licznikiem odwołań na dany zasób, a w momencie jego spadku do 0, likwidować dany zasób.