Dziś tak bardziej o C++. Dość często widzę kod mający w zamierzeniu obsługiwać macierze wyglądający mniej więcej tak:
int** matrix = new int*[h]; for(int i = 0; i < h; ++i) { matrix[i] = new int[w]; } |
Pomijając nawet kwestię wydajności (to taka macierz JP na 100%, gdzie P to Prefetch), jest to okropny kod. Korzystanie z nagiego new i delete to antyidiom w C++ (szczególnie delete, szerzej opisałem to n.p. tutaj i tutaj). Jest to:
- niewygodne: kłopotliwa inicjalizacja, kolejność rząd/kolumna wymaga sprawdzenia sposobu inicjalizacji,
- niebezpieczne: brak RAII, trzeba pamiętać o odpowiednim zwolnieniu zasobów, można niechcący nadpisać wskaźnik jednego rzędu innym rzędem,
- niezgrabne: nie ma żadnego mechanizmu upewniającego, że wszystkie wiersze (lub kolumny) macierzy będą tej samej długości.
Niektórzy sugerują użycie vector<vector<T>>, ale to tak naprawdę rozwiązuje wyłącznie drugi z wyżej wymienionych problemów (będąc jeszcze gorszym dla cache – vector to de facto trzy wskaźniki). Faktycznie, może być on uznany za najbardziej istotny, ale korzystnym by było również rozwiązanie pozostałych. W przypadku gdy znamy jeden lub dwa wymiary macierzy w czasie kompilacji możemy użyć vector<array<T, N>> lub array<array<T, N>, M>. Jeśli jednak tak nie jest, cały czas istnieje możliwość użycia ciągłego bufora (przyjaznego dla cache) i prostego widoku na dane jako na macierz 2D. Przykładowo:
vector<double> data{1,2,3,4,5,6,7,8,9,10,11,12}; simple_2d_matrix_view<double> m(data.data(), 2, 6); for(size_t i = 0; i < m.height(); ++i) { for(size_t j = 0; j < m.width(); ++j) { cout << m(i, j) << " "; } cout << "\n"; } |
Implementacja użytego wyżej szablonu klasy simple_2d_matrix_view może wyglądać tak:
template<typename T> class simple_2d_matrix_view { T* data_; size_t width_; size_t height_; public: simple_2d_matrix_view(T* ptr, size_t h, size_t w): data_{ptr}, width_{w}, height_{h} {} size_t width() const { return width_; } size_t height() const { return height_; } T& operator()(size_t h, size_t w) { assert(w < width_); assert(h < height_); return data_[width_ * h + w]; } T const& operator()(size_t h, size_t w) const { return const_cast<simple_2d_matrix_view&>(*this)(h, w); } }; |
Bardzo fajny bajer. Ja do tej pory używałem aliasu using Mtrx = vector< i jakoś żyłem xD.
A co sądzisz o boost::multiarray?