Moduł Base

Nagłówek: Base.hpp
Elementy modułu: Base Module

Wstęp

Ten moduł to moduł bazowy. Plik Base.hpp włączają wszystkie pozostałe pliki biblioteki i użytkownik również jest spodziewany aby to robić przed włączaniem wszelkich innych nagłówków biblioteki, ponieważ korzystają one ze zdefiniowanych w Base.hpp typów i innych elementów, a same go nie włączają.

Nie używa Error ani żadnego innego modułu. To inne jego używają. Żadne z jego funkcji nie zgłaszają błędów przez wyjątki.

Składniki

absolute_cast

To brakujący w C++ operator dosłownej reinterpretacji bitowej między wartościami dowolnego typu. Ma sens tylko w przypadku typów o takim samym rozmiarze w sensie liczby zajmowanych bajtów. Jest to sprytnie napisana funkcja szablonona, której można używać niczym wbudowanych operatorów rzutowania C++ takich jak static_cast czy reinterpret_cast. Jej wynalazcą jest Karol Kuczmarski "Xion". Przykład:

float F = 10.5f;
uint4 I1 = static_cast<uint4>(F);
uint4 I2 = absolute_cast<uint4>(F);

I1 będzie wynosiło 10 - nastąpiła konwersja wartości liczbowej. I2 będzie wynosiło 1093140480 - nastąpiła dosłowna reinterpretacja bitów.

Więcej informacji na temat problemu konwersji między typami w C++ znajdziesz w moim artykule "Rzutowanie typów w C++", Adam Sawicki, Czerwiec 2006, http://regedit.gamedev.pl/produkcje/artykuly/rzutowanie_typow_w_cpp.php5

Konwersje

Moduł Base oferuje konwersje między różnymi typami a łańcuchami w obydwie strony, także z dodatkowymi opcjami. Służą do tego funkcje takie jak np.: common::IntToStr, common::StrToInt, common::FloatToStr, common::StrToFloat, common::BoolToStr, common::StrToBool itd.

Uogólnioną wersją tych konwersji są szablony funkcji SthToStr i StrToSth. Obsługują one następujące typy:

Próba użycia tych funkcji dla nieobsługiwanego typu zakończy się Assertion Failed. Można sprawdzić, czy dany typ jest obsługiwany używając:

bool B1 = StrToSth_obj<SOME_TYPE>::IsSupported();
bool B2 = SthToStr_obj<SOME_TYPE>::IsSupported();

Można pisać konwersje do i z własnych typów. Szablon takiej konwersji jest następujący. Należy zamienić T na konkretny typ i wpisać konwersję w miejscu //. Kod ten należy wpisać poza jakąkolwiek przestrzenią nazw.

template <>
struct SthToStr_obj<T>
{
  void operator () (string *Str, const T &Sth)
  {
    //
  }
  static inline bool IsSupported() { return true; }
};

template <>
struct StrToSth_obj<T>
{
  bool operator () (T *Sth, const string &Str)
  {
    //
  }
  static inline bool IsSupported() { return true; }
};

Jeśli StrToSth się nie uda, wartość parametru wyjściowego jest niezdefiniowana.

Moduł Error rozszerza ten mechanizm o szablon funkcji MustStrToSth, który w przypadku niepowodzenia rzuca wyjątek.

Klasa common::Format (wraz z przeładowanym dla niej operatorem %) robi za sprintf dla łańcuchów string. Nie jest super szybka, ale jest wygodna w użyciu. Obsługuje wszystkie te typy, co SthToStr. Sposób użycia (jeśli jakiśtam OutputLine oczekuje const string&):

OutputLine( Format("Błąd w pliku # w wierszu #") % "plik.txt" % 123 );

Jeśli potrzeba zapisać do strumienia lub z innych przyczyn operator string() nie działa, używamy metody str:

std::cout << ( Format("Błąd w pliku # w wierszu #") % "plik.txt" % 123 ).str();

Jeśli chcemy niestandardowe formatowanie podczas konwersji z jakiegoś typu na łańcuch, konwertujemy we własnym zakresie:

OutputLine( Format("Błąd w pliku # w wierszu #") % "plik.txt" % IntToStr2R(123, 5) );

Jeśli potrzebujemy znaku '#' i chcemy jako specjalny użyć innego znaku:

OutputLine( Format("Błąd # % w pliku %", '%') % 123 % "plik.txt" );

Inteligentne wskaźniki

Moduł zawiera własną, elastyczną i dość potężną, acz prostą implementację inteligentnych wskaźników.

Sposób użycia

Są cztery rodzaje wskaźników:

Są trzy polityki zwalniania dla wskaźników:

Oraz dwie dla uchwytów [tylko Windows]:

Można też pisać własne polityki zwalniania. Przykłady:

Przykłady klas wskaźników:

Sposób użycia common::scoped_ptr:

- Konstruktor domyślny ustawia wskaźnik pusty (NULL)
    scoped_ptr<Klasa> p1;
- Konstruktor inicjalizujący (tylko explicit):
    scoped_ptr<Klasa> p1(NULL);
    scoped_ptr<Klasa> p2(new Klasa());
- Wyzerowanie wskaźnika (jeśli trzeba, sam zwolni obiekt):
    p1.reset();
    p1.reset(NULL);
- Ustawienie wskaźnika na nowy obiekt (jeśli trzeba, sam zwolni stary):
    p1.reset(new Klasa());
- Odwołanie się pod wskaźnik:
    Klasa obj2 = *p1;
    p1->MetodaKlasy();
- Otrzymanie prawdziwego wskaźnika:
    Klasa *RealPtr = p1.get();
- Porównywanie wskaźników:
    if (p1 == NULL) { }
    if (RealPtr == p1) { }
- Zamiana:
    p1.swap(p2);
    swap(p1, p2);
- Odwołanie się do elementu tablicy wskazywanej przez wskaźnik:
    scoped_ptr<int, DeleteArrayPolicy> tablica(new int[1024]);
    tablica[0] = 1;

Sposób użycia shared_ptr:

- Wspiera to samo co scoped_ptr plus dodatkowo...
- Skopiowanie wskaźnika do drugiego:
  Działa także dla klas sparametryzowanych typem klasy nieidentycznym, ale
  dającym się skonwertować.
    shared_ptr<Klasa> p1;
    shared_ptr<Klasa> p2 = p1;
    p2 = p1;
    p2.reset(p1);
- Porównanie dwóch wskaźników:
    if (p1 == p2) { }

Sposób użycia common::scoped_handle i common::shared_handle: Jak odpowiednio common::scoped_ptr lub common::shared_ptr, ale:

Szczegóły

Inteligetne wskaźniki są potrzebne, zwłaszcza do unikania wycieków pamięci kiedy używamy wyjątków. Do napisania własnych zmotywowała mnie chęć uwolnienia się od biblioteki Boost. Pisanie takich wskaźników nie jest trudne, ale wiąże się z podjęciem szeregu trudnych decyzji projektowych. Podjąłem następujące:

Singleton

Szablon klasy common::Singleton to klasa bazowa do implementacji wzorca projektowego singletonu, czyli klasy, której główny (najczęściej jedyny) obiekt jest tworzony automatycznie podczas pierwszego użycia.

Deklarujemy tak:

class MyClass : public common::Singleton<MyClass>
{
  int MyMethod(int x);
};

Używamy tak:

int i = MyClass::GetInstance().MyMethod(123);

Obiekt powstanie przy pierwszym użyciu i, jeśli kiedykolwiek powstał, zostanie na końcu programu automatycznie usunięty (wywoła się destruktor).

Parser wiersza poleceń

Klasa common::CmdLineParser oferuje parsowanie parametrów przekazanych do programu z wiersza poleceń. Jest trochę podobna do funkcji getopt z Linuksa.

Cechy

Sposób użycia

  1. Utworzyć obiekt klasy common::CmdLineParser
    Jako parametry do odpowiedniego konstruktora podać argumenty wiersza poleceń otrzymane w funkcji main lub WinMain. W czasie parsowania nie mogą się zmieniać.
  2. Zarejestrować dopuszczalne opcje metodą common::CmdLineParser::RegisterOpt. Każda opcja ma:
    • Identyfikator liczbowy - powinien być większy od 0.
    • Treść - jednoznakową (opcja krótka) lub wieloznakową (opcja długa)
    • Flagę, czy oczekuje parametru (dodatkowego łańcucha za opcją)
  3. Pobierać kolejne informacje metodą common::CmdLineParser::ReadNext aż do otrzymania common::CmdLineParser::RESULT_END lub common::CmdLineParser::RESULT_ERROR.

Składnia

Użycie parsera polega na pobieraniu kolejnych informacji. Możliwe wartości:

Na przykład jeśli 'a', 'b' i 'c' to opcje krótkie, z których 'c' oczekuje parametru, dopuszczalne są m.in. takie konstrukcje:

-a -b -c parametr
-abc parametr
-a -b -c=parametr
-ab -c"parametr ze spacją"
"-abcparametr ze spacją"
/a /b /c parametr
/a /b /c="parametr ze spacją"
/a /b "/cparametr ze spacją"

Jeśli natomiast "AA" i "BBB" to opcje długie, z czego ta druga oczekuje parametru, dopuszczalne są m.in. takie konstrukcje:

--AA --BB parametr
--AA --BB=parametr
/AA /BB="parametr ze spacją"
--AA /BB "parametr ze spacją"

Kolejność opcji nie jest w żaden sposób kontrolowania, podobnie jak ich obecność czy powtarzanie się wiele razy. To leży już w gestii użytkownika - parser tylko odczytuje i zwraca kolejne napotkane informacje.

Przykład

Obsługiwana składnia jest skomplikowana i oparta na dogłębnych badaniach zachowania wiersza poleceń Windowsa i Linuksa. Niektóre możliwości przedstawia ten przykład. Jeśli zarejestrowane są opcje:

RegisterOpt(1, 'a', false);
RegisterOpt(2, 'b', false);
RegisterOpt(3, 'c', true);
RegisterOpt(11, "AA", false);
RegisterOpt(12, "BBB", true);

Wiersz poleceń wygląda tak:

-a -b -c param -abc="param" "-cparam" /AA --AA "/BBB"=param DUPA --BBB "param"

Wówczas wynikiem kolejnych wywołań ReadNext jest:

Result=RESULT_OPT,       Id=1,  Param=""
Result=RESULT_OPT,       Id=2,  Param=""
Result=RESULT_OPT,       Id=3,  Param="param"
Result=RESULT_OPT,       Id=1,  Param=""
Result=RESULT_OPT,       Id=2,  Param=""
Result=RESULT_OPT,       Id=3,  Param="param"
Result=RESULT_OPT,       Id=3,  Param="param"
Result=RESULT_OPT,       Id=11, Param=""
Result=RESULT_OPT,       Id=11, Param=""
Result=RESULT_OPT,       Id=12, Param="param"
Result=RESULT_PARAMETER, Id=0,  Param="DUPA"
Result=RESULT_OPT,       Id=12, Param="param"
Result=RESULT_END,       Id=0,  Param=""

Generated on Wed Dec 16 20:44:52 2009 for CommonLib by  doxygen 1.6.1