Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to create a contiguous 2d array in c++?

Tags:

c++

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

It is not a problem to create the array using the command:

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

However, I am not sure how to return this array as a general type for a function. The function is:

  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 don't work. I wouldn't want to use double*, since I want to access this array from outside as a 2D array.

Related question: How do I declare a 2d array in C++ using new?

like image 789
kloop Avatar asked Feb 21 '14 19:02

kloop


Video Answer


2 Answers

If you want to create an array where the data is contiguous and you don't want a 1-dimensional array (i.e. you want to use the [][] syntax), then the following should work. It creates an array of pointers, and each pointer points to a position into a pool of memory.

#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";
   }
}

Note that only 2 allocations are done. Not only is this more efficient due to the lesser amounts of allocations done, we now have a better chance of doing a rollback of the allocated memory if a memory allocation fails, unlike the "traditional" way of allocating a 2D array in 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 during the operation of the for loop, you have to roll back all of the successful calls to new[] that happened previously -- that requires more code and adds complexity.

Note how you deallocate the memory in the contiguous version -- just two calls to delete[] when allocated contiguously instead of a loop calling delete[] for each row.


Also, since the data is in contiguous memory, algorithms, functions, etc. that assume that the data is in contiguous memory, just like a one-dimensional array, can now be used by specifying the start and end range for the M*N matrix:

[&array[0][0], &array[M-1][N])

For example:

std::sort(&myArray[0][0], &myArray[M-1][N]);

will sort the entire matrix in ascending order, starting from index [0][0] up until the last index [M-1][N-1].

You can improve on the design by making this a true class instead of having allocation / deallocation as 2 separate functions.


Edit: The class is not RAII-like, just as the comment says. I leave that as an exercise for the reader. One thing missing from the code above is the check that nRows and nCols are > 0 when creating such an array.

Edit 2: Added a try-catch to ensure a proper roll back of the memory allocation is done if a std::bad_alloc exception is thrown attempting to allocate memory.


Edit: For a 3 dimensional array example of code similar to the above see this answer. Included is code to roll back allocations if the allocation fails.


Edit: Rudimentary RAII class added:

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] << " " << dPtr[1][1] << "\n";
    }
    catch (std::exception& ex)
    {
        std::cout << ex.what();
    }
}
 
like image 168
PaulMcKenzie Avatar answered Oct 21 '22 21:10

PaulMcKenzie


Unless the size of the two dimensions is known at compile time, your don't have much choice: allocate a single rows*cols array of ints, and roll your own 2D indexing with integer multiplication and addition. Wrapping this in a class can produce a nice-looking syntax for accessing array elements with square bracket operator. Since your array is 2D, you will need to use proxy (AKA "surrogate") objects for the first level of data access.

Here is a small sample code that uses std::vector<T> for maintaining a contiguous memory region 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);
    }
};

You can now use Array2D<int> as if it were a built-in C++ 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;
    }
}

Running demo on ideone.

like image 8
Sergey Kalinichenko Avatar answered Oct 21 '22 20:10

Sergey Kalinichenko