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

后端 未结 7 1503
走了就别回头了
走了就别回头了 2020-11-21 23:52

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] = n         


        
7条回答
  •  慢半拍i
    慢半拍i (楼主)
    2020-11-22 00:38

    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 
    #include 
    
    template 
    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 
    void delete2DArray(T** arr)
    {
       delete [] arr[0];  // remove the pool
       delete [] arr;     // remove the pointers
    }
    
    int main()
    {
       try 
       { 
          double **dPtr = create2DArray(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 
    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 dPtr(10, 10);
            std::cout << dPtr[0][0] << " " << a2[0][0] << "\n";
        }
        catch (std::exception& ex)
        {
            std::cout << ex.what();
        }
    }
    

提交回复
热议问题