Posts tagged ‘c/c++’

Tak jak zapowiadałem w moim poprzednim wpisie, rvalue_converter operujący na streamach jest też, a jakże. Krótkie wprowadzenie: mamy sobie wyeksportowane api, które m.in. prowadzi zapis danych do pliku, ale aby odciążyć sama metodę od żmudnych operacji na strumieniach, a jednocześnie dać jej dużą elastyczność dot. zapisu danych, dostaje ona gotową referencję do strumienia jako parametr, przykładowy kod może wyglądać tak:

class MyClass {
public:
writeToStream( std::ifstream& out ){}
}

No i teraz, fajnie by było mieć możliwość otwarcia pliku po stronie pythona i tylko przesłania samego uchwytu do naszej metody c++’owej. Otóż Ci którzy czytali poprzedni wpis o konwersji stringów już pewnie wiedzą, że to jest możliwe, może troche karkołomne, bo synchronizacja streamów to sprawa niebalna, no ale w moich testach wszystko działa poprawnie.

A oto i kod:

namespace tulips { 
namespace converters { 
namespace fstream {

  class custom_fstream
  {
    public:
		custom_fstream(
			const char* path,
			FILE* file,
			std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out
		) :
			m_fstream(path,mode),
			m_file(file)
		{
			fflush(m_file);
			m_fstream.seekg(ftell(m_file));
		}
		std::fstream& get_stream() { return m_fstream; }
		~custom_fstream(){ fseek( m_file, m_fstream.tellg(), SEEK_SET );}
    private:
      std::fstream m_fstream;
      FILE* m_file;
  };

  struct custom_fstream_from_python_file
  {
	custom_fstream_from_python_file()
	{
		bp::converter::registry::push_back(
			&convertible,
			&construct,
			bp::type_id<custom_fstream>());
	}

	static void* convertible(PyObject* obj_ptr)
	{
		if (!PyFile_Check(obj_ptr) || obj_ptr == Py_None) return 0;
		return obj_ptr;
	}

	static void construct(PyObject* obj_ptr, bp::converter::rvalue_from_python_stage1_data* data)
	{
		FILE* file = PyFile_AsFile(obj_ptr);
		void* storage = ((bp::converter::rvalue_from_python_storage<custom_fstream>*)data)->storage.bytes;
		new (storage) custom_fstream(PyString_AsString(PyFile_Name(obj_ptr)), file, std::ios::in|std::ios::out);
		data->convertible = storage;
	}
  };

  void init_converter()
  {
    custom_fstream_from_python_file();
  }

}}} // namespace tulips::converters::stream

No i teraz już możemy sobie przesyłać uchwyt do strumienia otwarty po stronie pythona 🙂

Wszyscy programujący w c/c++ wiedzą, że zarządzanie pamięcią to nie jest sprawa prosta. Załóżmy, że mamy sobie obiekt grupujący jakies pola, powiedzmy, że będzie to klasa Header, i ma mapę Fieldów  (std::map<std::string,Field>) identyfikowanych po nazwie. No i chcemy, żeby Field’y można było modyfikować poza Header’em, czyli mapa musi być dostępna przez publiczne api. Możnaby coprawda udostępnić tylko kopię referencji w mapie, ale tracimy cała elastyczność związaną z bezpośrednimi modyfikacjami Fieldów. No tak, tylko teraz mamy iterację po wskaźnikach na obiekty Field, przykładowy kod może wyglądać tak:

for( FieldMap::iterator it = fieldMap.iterator(); it != fieldMap.end(); it++ ) {

Field* f = *it;

}

Wygląda prosto, ale co jeżeli ktoś zechce nam taki wskaźnik usunąć, albo zastąpić go innym wskaźnikiem? To tylko 2 przykładowe problemy z jakimi sie można napotkać stosując powyższe rozwiązanie. Pewnie zaraz puryści dodadzą, żeby powstawiać całą masę const‚ów, które owszem rozwiążą część problemów, ale same stworzą kolejne.

Zamiast żonglować const’ami, można zastosować smart pointer’y. Czymże one sie charakteryzują – otóż w swej najprostszej postaci, przewowuja nam wskaźnik i udostepniają na żądanie, usuwając go w momencie usunięcia samego siebie (std::auto_ptr) + przechowują prawo własności do wskaźnika. W bardziej skomplikowanej postaci, posiadają wewnętrzny licznik referencji, dzięki któremu mogą stwierdzić czy dany obiekt nadaje sie do usunięcia czy nie (boost::shared_ptr). Żeby jeszcze sobie uprościć życie, można zezwolić na konstrukcję „pustych” obiektów shared_ptr i wypełniać je wskaźnikami dopiero na żadanie.

To w sumie tyle wprowadzenia, po więcej odsyłam do dokumentacji boost’a – nie będe jej tutaj powielał, natomiast moim celem jest przekazanie paru problemów, z którymi sie napotkałem podczas pracy z boost::shared_ptr.

1. Konstruktor kopiujący a nieusuwalny boost::shared_ptr.

W manualu boost’owym podają, że jeżeli potrzebujemy stworzyć inteligentny wskaźnik, który nie będzie usuwany, bo np zarządzamy nim gdzie indziej, a referencji z jakiegoś względu nie możemy przekazać – można do konstruktora przesłać jako dodatkowy argument funktor, który zapewni nam, że podczas dojścia licznika referencji do 0, nic się nie stanie.

Uzbrojeni w taka broń smiało przekazywaliśmy nasz obiekt to tu to tam, aż w pewnym momencie ni z gruchy ni z pietruchy pokazał sie  seg fault. Otóż okazało się, że nasz konstruktor kopiujący owszem kopiował wskaźnik, zwiększał licznik referencji, ale o zgrozo, NIE KOPIOWAŁ funktora, przez co nasz nieusuwalny wskaźnik stawał sie zwykłym shared_ptr’em i w momencie dojścia licznika do 0, został skasowany.

2. Nasza przykładowa aplikacja udostępnia swoją mapę, światowi zewnętrznemu, ale tenże swiat ingerując w stan jej elementów, może nam popsuć sporo i aby temu zaradzić można wprowadzić obserwatora, w momencie dodania fielda, kontener (w tym przypadku Header) rejestruje sie jako obserwator fieldów, a one wysyłają notyfikację, jak ktoś lub coś je zmienia.

Problem jaki się pojawił był związany właśnie z obserwatorem. Otóż w pewnym momencie pochodne Headera służyły jednynie za twórcę mapy, która miała być przekazana dalej, jednak rejestrowały sie jako obserwatory, a w momencie usunięcia nie wyrejestrowywały się, więc mapa lądowała w innej strukturze, ze wskaźnikiem na zwolniony obszar w pamięci.

Dodanie autousuwania obserwatora ze wszystkich fieldów w destruktorze naprawiło sytuację.

Ten krótki artykuł nie jest próbą dyskredytacji smart pointerów a jedynie, formą dzielenia się wiedzą, bo skoro zdażyło sie to raz, to pewnie komuś sie zdarzy drugi raz.

Pozdrawiam

Kontenery STL’owe – std::vector, std::map, przydatne i miłe, i nie ma w tym stwierdzeniu nic rewolucyjnego, ale jest jeszcze jeden kontener, który może ułatwić nam życie – mianowicie std::basic_string.

Jakie basic_string? jest string i już!

Otóż std::string to tylko specjalizacja szablonu std::basic_string, może wyglądać następująco:

typedef std::basic_string<char> string;

No ale na co to komu potrzebne?

Załóżmy, że mamy aplikację która odczytuje dane binarne z pliku. Moglibyśmy stosować wskaźnik na tablice (const unsigned char*), ale wtedy wszystkie operacje na stringach trzeba wykonywać przy pomocy api odziedziczonego z C, a chyba nie całkiem o to chodzi, bo jeżeli nasz ciąg będzie zawierał null‚e jako separatory to już nie bedzie tak fajnie. No ale przecież można skorzystać z wektora i nie zawracać sobie głowy jakimśtam basic_string. Owszem można, ale dziedzicząc po basic_string otrzymujemy wszystkie metody dostępne dla std::string i tak może to wyglądać:

typedef std::string<unsigned char>  ustring;

ustring line;

// tu sobie wczytujemy, zmienna line ma 300 znaków

// a na chwilę obecną potrzebujemy ich 30

ustring elem =  line.substr( 0, 30 );

// i kolejne 30

ustring elem1 = line.substr( 30, 60 );  // itd

To jako podsumowanie jeszcze tylko pobieranie tablicy którą kontener zarządza:

const unsigned char* arr = line.c_str();

Pewnie jeszcze możnaby się rozpisywać o wstring, ale to już zostawię do samodzielnego zgłębienia czytelnikowi.