3
\$\begingroup\$

I need to allocate n-dimensional arrays of type T, currently I am using the following functions. Is there a better way to do this? Also, ideally I would like to allocate a 1-D array and access that as a N-D array but I could not figure out a better way of doing that. Using existing matrix library is not an option as I want access to raw pointers.

template <class T, class Ti>
T*** allocate_3d_array(Ti nx, Ti ny, Ti nz){
    T*** A = new T**[nx];
    for(Ti i(0); i < nx; ++i){
            A[i] = new T*[ny];
            for(Ti j(0); j < ny; ++j){
                A[i][j] = new T[nz];                    
                for(Ti k(0); k < nz; ++k){
                    A[i][j][k]= 0.;
                }
            }
    }
    return A;
}
template <class T, class Ti>
void release_3d_array(T*** A, Ti nx, Ti ny, Ti nz){
    for (Ti i = 0; i < nx; ++i){
            for (Ti j = 0; j < ny; ++j){
                delete[] A[i][j];
            }
            delete[] A[i];
    }
    delete[] A;
}

template <class T, class Ti>
T** allocate_2d_array(Ti nx, Ti ny){
    T** A = new T*[nx];
    for(Ti i(0); i < nx; ++i){
        A[i] = new T[ny];
    }
    return A;
}

template <class T, class Ti>
void release_2d_array(T** A, Ti nx, Ti ny){
    for (Ti i = 0; i < nx; ++i){
        delete[] A[i];
    }
    delete[] A;
}


template <class T, class Ti>
T* allocate_1d_array(Ti nx){
    T *A = new T[nx];
    return A;
}

template <class T, class Ti>
    void release_1d_array(T* A, Ti nx){
    delete[] A;
}
\$\endgroup\$
2
\$\begingroup\$

Answering Questions.

I need to allocate n-dimensional arrays of type T, currently I am using the following functions. Is there a better way to do this?

Yes. Create a class that wraps the 2/3 D array in such a way that memory management is done correctly in an exception safe manner.

You may need the raw pointer for interfacing with other libraries but that does not need to mean you should actively manage the pointer. You need to wrap these in a class so all the memory management for you but provide access to the RAW pointer for situations when you need it.

Example:

template<typename T>
class Array3D
{
    private:
    static T*** allocate_3d_array(std::size_t nx, std::size_t ny, std::size_t nz)
    {
        T*** A = new T**[nx];
        for(std::size_t i = 0; i < nx; ++i)
        {
            A[i] = new T*[ny];
            for(std::size_r j(0); j < ny; ++j)
            {
                A[i][j] = new T[nz];                    
                for(std::size_t k(0); k < nz; ++k)
                {
                    A[i][j][k]= 0.;
                }
            }
        }
        return A;
    }
    static void release_3d_array(T*** A, std::size_t nx, std::size_t ny, std::size_t nz)
    {
        for (std::size_t i = 0; i < nx; ++i)
        {
            for (std::size_t j = 0; j < ny; ++j)
            {
                delete[] A[i][j];
            }
            delete[] A[i];
        }
        delete[] A;
    }
    T*** data;
    std::size_t  xSize;
    std::size_t  ySize;
    std::size_t  zSize;
public:
    Array3D(std::size_t xSize, std::size_t ySize, std::size_t zSize)
        : data(allocate_3d_array(xSize, ySize, zSize)
        , xSize(xSize)
        , ySize(ySize)
        , zSize(zSize)
    {}
    ~Array3D()
    {
        release_3d_array(data, xSize, ySize, zSize);
    } 

    // Disable Copy for now
    Array3D(Array3D const&)            = delete;
    Array3D& operator=(Array3D const&) = delete;

    // Disable Move for now
    Array3D(Array3D&&)                 = delete;
    Array3D& operator=(Array3D&&)      = delete;


    // Allow access to the underlying data when calling C function.
    operator T***()
    {
        return data;
    }
};

Now you can create a 3D array where the memory is correctly managed at all times (so no leaks). When you pass the data to a C function it will convert itself to a T*** so that it can be used (I am assuming that the C functions are non owning and will thus not destroy the array and the object is in the same thread and will thus live as long as the function call).

If you want to use operator[] to access the object See below were I have linked to a stackoverflow article.

Copy is probably not something you want with a 3D array. But if you need it you will need to implement the appropriate functions above.

Move is an advanced form of copy. don't use until you understand move semantics. Again you will need to implement the above functions.

Also, ideally I would like to allocate a 1-D array and access that as a N-D array but I could not figure out a better way of doing that.

Yes this would makes memory usage more effecient so would be the preferred technique to implement a multidimensional arrya. I refer you to an article I have written on SO that points you in the correct direction.

See: How to overload array index operator for wrapper class of 2D array? for an example.

Using existing matrix library is not an option as I want access to raw pointers.

To be honest I am bit surprised by this statement. In my mind this is a requirement of any library that wants to interface with C libraries. Not having this ability is shocking. Maybe you missed it. What library were you thinking of using?

Your current style is definitely lacking in relation to C++. The way you have implemented this provides no benefits to using C++ over C.

Generic Review

I see no need for the template type Ti

template <class T, class Ti>
T*** allocate_3d_array(Ti nx, Ti ny, Ti nz){

In my mind this is always an integer type. Pick one. If you did this for some specific reason that I have not deduced then this should be pointed out in the comments of the code (if it is not obvious to me then I don't think anybody else got it).

Use zero initialization.

new T;  // Alocates memory for T but does default initialization.
        // for POD types this means nothing is done and the value is
        // indeterminate.

new T();// Allocates memory for T but does zero initialization.
        // for pod types it means the values have zero.


            A[i][j] = new T[nz];                    
            for(Ti k(0); k < nz; ++k){
                A[i][j][k]= 0.;
            }

            // Can be replaced with:
            A[i][j] = new T[nz]();
\$\endgroup\$
  • 2
    \$\begingroup\$ I agree with the details here, but this review feels needlessly hostile. \$\endgroup\$ – cmh Aug 19 '16 at 20:56
  • \$\begingroup\$ @cmh: I hope that improves the read. \$\endgroup\$ – Martin York Aug 19 '16 at 23:07
1
\$\begingroup\$

I will add some more on top of Loki's answer. The main thing you left out there is type safety. By making those indirections you're basically stripping off everything the standard library templated part can offer to you. On top of that, decltype(), sizeof() and many other functions that rely on compile time type information are rendered useless. It is crucial thing to be fixed.

Suggested implementation

So, first of all we need to find out how can we make convenient type/type alias to generate for us array type with specified dimensions?

The word type suggests that it needs to be compile time and the best (probably the only) way is template metaprogramming.

The way it is done is as follows: we take a type and a sequence of dimensions (e.g. int, 3, 4, 5) and starting from the end, append every dimension to the type.

#ifndef GENERATE_DIMENSIONS_H
#define GENERATE_DIMENSIONS_H
#include <cstddef>

template <typename T, std::size_t first, std::size_t ... rest>
struct generate_dimensions
{
    using type = typename generate_dimensions<T, rest...>::type[first];
};

template <typename T, std::size_t first>
struct generate_dimensions<T, first>
{
    using type = T[first];
};

template <typename T, std::size_t first, std::size_t ... rest>
using generate_dimensions_t = typename generate_dimensions<T, first, rest...>::type;

#endif

The recursion is stopped when it reaches single dimension case, and appends other dimensions on the way back. The allocation of the array is trivial:

new generate_dimensions_t<int, 3, 4, 5>;

In case you want to defer construction, we can write this:

operator new(sizeof(generate_dimensions_t<int, 3, 4, 5>));

The size of the array type is calculated correctly, which is one of our goals.

This is pretty much it. Stay calm, and embrace C++.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.