How to create an adjacent 2d array in C ++?

I want to create a function that returns an adjacent 2D array in C ++.

Unable to create an array using the command:

int (*v)[cols] = new (int[rows][cols]); 

However, I'm not sure how to return this array as a generic type for a function. Function:

  NOT_SURE_WHAT_TYPE create_array(int rows, int cols) { int (*v)[cols] = new (int[rows][cols]); return v; } 

I tried double * [] and double **, and both of them do not work. I would not want to use double *, since I want to access this array from the outside as a 2D array.

Related questions: How to declare a 2d array in C ++ using the new?

+10
source share
6 answers

If you want to create an array in which data is contiguous, and you do not need a one-dimensional array (that is, you want to use the syntax [][] ), then the following should work. It creates an array of pointers, and each pointer points to a position in the memory pool.

 #include <iostream> #include <exception> template <typename T> T** create2DArray(unsigned nrows, unsigned ncols, const T& val = T()) { if (nrows == 0) throw std::invalid_argument("number of rows is 0"); if (ncols == 0) throw std::invalid_argument("number of columns is 0"); T** ptr = nullptr; T* pool = nullptr; try { ptr = new T*[nrows]; // allocate pointers (can throw here) pool = new T[nrows*ncols]{val}; // allocate pool (can throw here) // now point the row pointers to the appropriate positions in // the memory pool for (unsigned i = 0; i < nrows; ++i, pool += ncols ) ptr[i] = pool; // Done. return ptr; } catch (std::bad_alloc& ex) { delete [] ptr; // either this is nullptr or it was allocated throw ex; // memory allocation error } } template <typename T> void delete2DArray(T** arr) { delete [] arr[0]; // remove the pool delete [] arr; // remove the pointers } int main() { try { double **dPtr = create2DArray<double>(10,10); dPtr[0][0] = 10; // for example delete2DArray(dPtr); // free the memory } catch(std::bad_alloc& ex) { std::cout << "Could not allocate array"; } } 

Please note that only 2 selections have been made. This is not only more efficient due to the smaller number of allocated allocations, but now we have more chances to roll back the allocated memory in the event of a memory allocation failure, in contrast to the β€œtraditional” way of allocating a two-dimensional array into non-contiguous memory:

 // The "traditional" non-contiguous allocation of a 2D array (assume N x M) T** ptr; ptr = new T*[N]; for (int i = 0; i < N; ++i) ptr[i] = new T [M]; // <<-- What happens if new[] throws at some iteration? 

If new[] throws an exception somewhere while the for loop is running, you must roll back all successful calls to new[] that occurred earlier β€” this requires more code and adds complexity.

Also pay attention to how you free up memory in the adjacent version - just two calls to delete[] when distributed sequentially instead of a loop calling delete[] for each row.

You can improve the design by making it a true class, instead of highlighting / freeing it as two separate functions.


Edit: the class is not RAII-like, as the comment says. I leave this as an exercise for the reader. In the code above, one thing is missing - checking that nRows and nCols> 0 when creating such an array.

Edit 2: try-catch added to ensure proper rollback of memory allocation if std::bad_alloc exception occurs when allocating memory.


Change: Example code with a three-dimensional array, similar to the one above , see this answer. Code is included to roll back resource allocation in case of distribution failure.


Change: added elementary class RAII:

 template <typename T> class Array2D { T** data_ptr; unsigned m_rows; unsigned m_cols; T** create2DArray(unsigned nrows, unsigned ncols, const T& val = T()) { T** ptr = nullptr; T* pool = nullptr; try { ptr = new T*[nrows]; // allocate pointers (can throw here) pool = new T[nrows*ncols]{ val }; // allocate pool (can throw here) // now point the row pointers to the appropriate positions in // the memory pool for (unsigned i = 0; i < nrows; ++i, pool += ncols) ptr[i] = pool; // Done. return ptr; } catch (std::bad_alloc& ex) { delete[] ptr; // either this is nullptr or it was allocated throw ex; // memory allocation error } } public: typedef T value_type; T** data() { return data_ptr; } unsigned get_rows() const { return m_rows; } unsigned get_cols() const { return m_cols; } Array2D() : data_ptr(nullptr), m_rows(0), m_cols(0) {} Array2D(unsigned rows, unsigned cols, const T& val = T()) { if (rows == 0) throw std::invalid_argument("number of rows is 0"); if (cols == 0) throw std::invalid_argument("number of columns is 0"); data_ptr = create2DArray(rows, cols, val); m_rows = rows; m_cols = cols; } ~Array2D() { if (data_ptr) { delete[] data_ptr[0]; // remove the pool delete[] data_ptr; // remove the pointers } } Array2D(const Array2D& rhs) : m_rows(rhs.m_rows), m_cols(rhs.m_cols) { data_ptr = create2DArray(m_rows, m_cols); std::copy(&rhs.data_ptr[0][0], &rhs.data_ptr[m_rows-1][m_cols], &data_ptr[0][0]); } Array2D(Array2D&& rhs) noexcept { data_ptr = rhs.data_ptr; m_rows = rhs.m_rows; m_cols = rhs.m_cols; rhs.data_ptr = nullptr; } Array2D& operator=(Array2D&& rhs) noexcept { if (&rhs != this) { swap(rhs, *this); rhs.data_ptr = nullptr; } return *this; } void swap(Array2D& left, Array2D& right) { std::swap(left.data_ptr, right.data_ptr); std::swap(left.m_cols, right.m_cols); std::swap(left.m_rows, right.m_rows); } Array2D& operator = (const Array2D& rhs) { if (&rhs != this) { Array2D temp(rhs); swap(*this, temp); } return *this; } T* operator[](unsigned row) { return data_ptr[row]; } const T* operator[](unsigned row) const { return data_ptr[row]; } void create(unsigned rows, unsigned cols, const T& val = T()) { *this = Array2D(rows, cols, val); } }; int main() { try { Array2D<double> dPtr(10, 10); std::cout << dPtr[0][0] << " " << a2[0][0] << "\n"; } catch (std::exception& ex) { std::cout << ex.what(); } } 
+25
source

If the size of these two dimensions is not known at compile time, you will not have much choice: select one rows*cols from int s and do your own two-dimensional indexing with integer multiplication and addition. Wrapping this in a class can create a nice syntax for accessing array elements using the square bracket operator. Since your array is 2D, you will need to use proxy objects (AKA surrogate) for the first level of data access.

Here is a small example code that uses std::vector<T> to maintain a contiguous region of memory in dynamic memory:

 template<class T> class Array2D { vector<T> data; size_t cols; public: // This is the surrogate object for the second-level indexing template <class U> class Array2DIndexer { size_t offset; vector<U> &data; public: Array2DIndexer(size_t o, vector<U> &dt) : offset(o), data(dt) {} // Second-level indexing is done in this function T& operator[](size_t index) { return data[offset+index]; } }; Array2D(size_t r, size_t c) : data (r*c), cols(c) {} // First-level indexing is done in this function. Array2DIndexer<T> operator[](size_t index) { return Array2DIndexer<T>(index*cols, data); } }; 

Now you can use Array2D<int> as if it were a C ++ inline array:

 Array2D<int> a2d(10, 20); for (int r = 0 ; r != 10 ; r++) { for (int c = 0 ; c != 20 ; c++) { a2d[r][c] = r+2*c+1; } } 

Launching a demo on Ideone .

+7
source

processing raw memory resources is often sloppy. The best shot is a simple wrapper:

 struct array2D : private std::vector<int> { typedef std::vector<int> base_type; array2D() : base_type(), height_(0), width_(0) {} array2D(std::size_t h, std::size_t w) : base_type(h*w), height_(h), width_(w); int operator()(std::size_t i, std::size_t j) const { return base_type::operator[](i+j*height_); } int& operator()(std::size_t i, std::size_t j) { return base_type::operator[](i+j*height_); } std::size_t rows() const { return height_; } std::size_t cols() const { return width_; } private: std::size_t height_, width_; } 

personal inheritance allows you to capture all the positive effects from the vector, just add your 2D constructor. Ressources management is free since the ctor / dtor vector will do its magic. Obviously, I + h * j can be changed to any desired storage order.

vector <vector <int β†’ is 2D, but will not be adjacent in memory.

Then your function will be as follows:

 array2D create_array(int rows, int cols) { return array2D(cols,rows); } 

EDIT:

You can also get other parts of the vector interface, such as start / end or size, using the usign clause to rediscover the functions of private inherited members.

+6
source

Since you are using C ++, not C, I would recommend using a single vector instead of messing with the new / remote.

You can define one contiguous block of memory as follows:

 std::vector<int> my_matrix(rows*cols); 

And now you get access to this vector in the form of a 2d array with the formula i * n + j, and I am the row index, j is the column index and n is the length of the row:

 my_matrix[i*n + j]; 

The same as accessing the 2d array with the [i] [j] array. But now you have the advantage of one continuous block of memory, you do not need to worry about new / delete, and you can easily exchange and return this vector object with functions.

+6
source

None of the ways to define a two-dimensional dynamic array in standard C ++ in my opinion is quite satisfactory.

You will have to minimize your own decisions. Fortunately, Boost already has a solution. boost :: multi_array :

 #include "boost/multi_array.hpp" template<typename T> boost::multi_array<T, 2> create_array(int rows, int cols) { auto dims = boost::extents[rows][cols]; return boost::multi_array<T, 2>(dims); } int main() { auto array = create_array<int>(4, 3); array[3][2] = 0; } 

Live demo .

+5
source

The Rudimentary RAll class provided by PaulMcKenzie is a great solution. In my use, I found a memory leak that was fixed in the version shown below.

A memory leak occurred due to a problem with Array2D& operator=(Array2D&& rhs) noexcept .

The operator rhs.m_dataPtr = nullPtr to be removed so that the rhs destructor can delete the original data (pool and pointers) replaced by lhs.

Here is the revised code for the "Rudimentary RAll" class provided by PaulMcKenzie

 template <typename T> class Array2D { T** data_ptr; unsigned m_rows; unsigned m_cols; T** create2DArray(unsigned nrows, unsigned ncols, const T& val = T()) { T** ptr = nullptr; T* pool = nullptr; try { ptr = new T*[nrows]; // allocate pointers (can throw here) pool = new T[nrows*ncols]{ val }; // allocate pool (can throw here) // now point the row pointers to the appropriate positions in // the memory pool for (unsigned i = 0; i < nrows; ++i, pool += ncols) ptr[i] = pool; // Done. return ptr; } catch (std::bad_alloc& ex) { delete[] ptr; // either this is nullptr or it was allocated throw ex; // memory allocation error } } public: typedef T value_type; T** data() { return data_ptr; } unsigned get_rows() const { return m_rows; } unsigned get_cols() const { return m_cols; } Array2D() : data_ptr(nullptr), m_rows(0), m_cols(0) {} Array2D(unsigned rows, unsigned cols, const T& val = T()) { if (rows == 0) throw std::invalid_argument("number of rows is 0"); if (cols == 0) throw std::invalid_argument("number of columns is 0"); data_ptr = create2DArray(rows, cols, val); m_rows = rows; m_cols = cols; } ~Array2D() { if (data_ptr) { delete[] data_ptr[0]; // remove the pool delete[] data_ptr; // remove the pointers } } Array2D(const Array2D& rhs) : m_rows(rhs.m_rows), m_cols(rhs.m_cols) { data_ptr = create2DArray(m_rows, m_cols); std::copy(&rhs.data_ptr[0][0], &rhs.data_ptr[m_rows-1][m_cols], &data_ptr[0][0]); } Array2D(Array2D&& rhs) noexcept { data_ptr = rhs.data_ptr; m_rows = rhs.m_rows; m_cols = rhs.m_cols; rhs.data_ptr = nullptr; } Array2D& operator=(Array2D&& rhs) noexcept { if (&rhs != this) { swap(rhs, *this); } return *this; } void swap(Array2D& left, Array2D& right) { std::swap(left.data_ptr, right.data_ptr); std::swap(left.m_cols, right.m_cols); std::swap(left.m_rows, right.m_rows); } Array2D& operator = (const Array2D& rhs) { if (&rhs != this) { Array2D temp(rhs); swap(*this, temp); } return *this; } T* operator[](unsigned row) { return data_ptr[row]; } const T* operator[](unsigned row) const { return data_ptr[row]; } void create(unsigned rows, unsigned cols, const T& val = T()) { *this = Array2D(rows, cols, val); } }; int main() { try { Array2D<double> dPtr(10, 10); std::cout << dPtr[0][0] << " " << a2[0][0] << "\n"; } catch (std::exception& ex) { std::cout << ex.what(); } } 
+1
source

Source: https://habr.com/ru/post/1012625/


All Articles