17

I'm trying to make a pointer point to a 2D array of pointers. What is the syntax and how would I access elements?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
TheFuzz
  • 2,607
  • 6
  • 29
  • 48
  • Is the size know at compile Time or created from fixed sizes at runtime? Are Rows and Columns fixed sizes or grown dynamically leading to jagged arrays? Are the pointers owned by the array or just held? Are the pointers all related (ie the same base type)? – Martin York Nov 20 '09 at 06:47

10 Answers10

25

By the letter of the law, here's how to do it:

// Create 2D array of pointers:
int*** array2d = new (int**)[rows];
for (int i = 0; i < rows; ++i) {
  array2d[i] = new (int*)[cols];
}

// Null out the pointers contained in the array:
for (int i = 0; i < rows; ++i) {
  for (int j = 0; j < cols; ++j) {
    array2d[i][j] = NULL;
  }
}

Be careful to delete the contained pointers, the row arrays, and the column array all separately and in the correct order.

However, more frequently in C++ you'd create a class that internally managed a 1D array of pointers and overload the function call operator to provide 2D indexing. That way you're really have a contiguous array of pointers, rather than an array of arrays of pointers.

Drew Hall
  • 28,429
  • 12
  • 61
  • 81
  • 1
    That's more like a pointer to an array of pointers to arrays. – Crashworks Nov 20 '09 at 05:03
  • 1
    It depends on what OP means by "2d array". If he simply means something that can be accessed with two subscript operators `a[n][m]`, then yes, this is duck-typed as a multidim array. If he means what the standard specifies as a 2D array, which is a contiguous region of n*m many elements, then a 1D array of pointers to 1D arrays is not the same thing. – Crashworks Nov 20 '09 at 08:46
  • @Martin York: First, the NULL assignment loop was for initialization (so we don't have a bunch of garbage pointers)--I wasn't trying to show deallocation, but mentioned in the text how to go about it. I also mentioned in my text that you'd usually wrap it in a class which would provide RAII (what I meant by "internally managed"), but I was specifically just trying to answer the direct question. I didn't touch the more metaphysical "should you even try to do this" issue... :) – Drew Hall Nov 20 '09 at 09:55
  • @Crashworks: The contiguous m*n array was what I was trying to suggest in my last paragraph--in retrospect it doesn't quite read that way but that's what I intended (hence the need to overload operator()). – Drew Hall Nov 20 '09 at 09:57
  • @Drew: Sorry got the NULL part wrong. That would be great for the answer to a C question. But this is C++. I would expect to see resource management and auto-cleanup of the array itself using RAII. (Note the OP does not specify if the data in the array is owned so cleaning up of the contained pointers need not be done (but the array should be able to clean up itself)). – Martin York Nov 20 '09 at 10:18
  • @Crashworks: The question is quite clear on what is needed. There is no requirement of contiguous storage. Only the ability to declare and access elements. – Martin York Nov 20 '09 at 10:30
8

It depends. It can be as simple as:

int main()
{
    int*   data[10][20]; // Fixed size known at compile time

    data[2][3] = new int(4);
}

If you want dynamic sizes at runtime you need to do some work.
But Boost has you covered:

int main()
{
   int  x;
   int  y;
   getWidthAndHeight(x,y);

   // declare a 2D array of int*
   boost::multi_array<int*,2>   data(boost::extents[x][y]);

   data[2][3] = new int(6);
}

If you are fine with jagged arrays that can grow dynamically:

int main()
{
    std::vector<std::vector<int*> >   data;

    data.push_back(std::vector<int*>(10,NULL));
    data[0][3] = new int(7);
}

Note: In all the above. I assume that the array does not own the pointer. Thus it has not been doing any management on the pointers it contains (though for brevity I have been using new int() in the examples). To do memory management correctly you need to do some more work.

Onyr
  • 769
  • 5
  • 21
Martin York
  • 257,169
  • 86
  • 333
  • 562
5
int *pointerArray[X][Y];
int **ptrToPointerArray = pointerArray;

That's how you make a true (contiguous in memory) multidimensional array.

But realize that once you cast a multidimensional array to a pointer like that, you lose the ability to index it automatically. You would have to do the multidimensional part of the indexing manually:

int *pointerArray[8][6]; // declare array of pointers
int **ptrToPointerArray = &pointerArray[0][0]; // make a pointer to the array
int *foo = pointerArray[3][1]; // access one element in the array
int *bar = *(ptrToPointerArray + 3*8 + 1); // manually perform row-major indexing for 2d array

foo == bar; // true
int *baz = ptrToPointerArray[3][1]; // syntax error
Crashworks
  • 40,496
  • 12
  • 101
  • 170
3
double** array = new double*[rowCnt];
for (int row = 0; row < rowCnt; ++row)
  array[row] = new double[colCnt];
for (int row = 0; row < rowCnt; ++row)
  for (int col = 0; col < colCnt; ++col)
    array[row][col] = 0;
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Maybe the array[row][col] syntax clarifies it? – Hans Passant Nov 20 '09 at 05:32
  • 1
    But it's still not contiguous. – Crashworks Nov 20 '09 at 06:38
  • 1
    @Crashworks: There was no requirement for the data to be contiguous. – Martin York Nov 20 '09 at 07:00
  • @nobugz: Still a very C like solution. The array should at least clean up itself. – Martin York Nov 20 '09 at 07:15
  • @Martin, well, the standard specifies a multidimensional array as contiguous (8.3.4). So, the requirement depends on what he meant by "2D array": if he means what the C++ standard calls a 2D array, then yes, it must be contiguous. If he just means something that has two subscripts, then heck, just use a `vector >`. – Crashworks Nov 20 '09 at 08:53
  • @Crashworks: Admittedly multidimensional is an overloaded term. But few people use the C standard as a definition. But the question is quite specific on requirements (non of which require contiguous elements). Yes Vector is a solution assuming Jagged arrays are fine. – Martin York Nov 20 '09 at 10:33
3

You could try Boost::MultiArray.

Check out this page for details.

Chip
  • 3,226
  • 23
  • 31
1

You can define a vector of vectors:

typedef my_type *my_pointer;

typedef vector<vector<my_pointer> > my_pointer2D;

Than create a class derived from my_pointer2D, like:

class PointersField: public my_pointer2D
{
  PointsField(int n, int m)
  {
     // Resize vectors....
  }
}

PointsField pf(10,10); // Will create a 10x10 matrix of my_pointer
Martin York
  • 257,169
  • 86
  • 333
  • 562
Elalfer
  • 5,312
  • 20
  • 25
  • 1
    Also, you shouldn't inherit from STL containers – Charles Salvia Nov 20 '09 at 04:56
  • @Charles: Shouldn't is too strong. Probably not a good idea. As the container does not have a virtual destructor. But under the write conditions (not these here) inheriting could be acceptable. – Martin York Nov 20 '09 at 07:28
1

:)

I had these once in a piece of code I wrote.

I was the laughing stock of the team when the first bugs leaked out. On top of that we use Hungarian notation, leading to a name like papChannel - a pointer to an array of pointers...

It's not nice. It's nicer to use typedefs to define a 'row of columns' or vice versa. Makes indexing more clear, too.

typedef int Cell;
typedef Cell Row[30];
typedef Row Table[20];

Table * pTable = new Table;

for( Row* pRow = *pTable; pRow != *pTable+_countof(*pTable); ++pRow ) {
   for( Cell* pCell = *pRow; pCell != *pRow + _countof(*pRow); ++pCell ) {
     ... do something with cells.
   }
}
xtofl
  • 40,723
  • 12
  • 105
  • 192
1

I prefer to use the () operator. There are lots of reasons for this (C++ FAQs 13.10). Change the internal representation to a std::vector if you like:

template <class T, int WIDTH, int HIEGHT>
class Array2d
{
public:
    const T& operator ()(size_t col, size_t row) const
    {
        // Assert col < WIDTH and row < HIEGHT
        return m_data [( row * WIDTH + col)];
    }
    T& operator ()(size_t col, size_t row)
    {
        // Assert col < WIDTH and row < HIEGHT
        return m_data [( row * WIDTH + col)];
    }
private:
T m_data[WIDTH * HIEGHT];
};

You can use it like this:

Array2d< Object*, 10, 10 > myObjectArray;
myObjectArray(5,6) = new Object();
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Charles Beattie
  • 5,739
  • 1
  • 29
  • 32
0

See my code. It works on my FC9 x86_64 system:

#include <stdio.h>

template<typename t>
struct array_2d {
        struct array_1d {
                t *array;
                array_1d(void) { array = 0; }
                ~array_1d()
                {
                    if (array) {
                         delete[] array;
                         array = 0;
                    }
                }
                t &operator[](size_t index) { return array[index]; }
        } *array;
        array_2d(void) { array = 0; }
        array_2d(array_2d<t> *a) { array = a->array; a->array = 0; }
        void init(size_t a, size_t b)
        {
                array = new array_1d[a];
                for (size_t i = 0; i < a; i++) {
                        array[i].array = new t[b];
                }
        }
        ~array_2d()
        {
                if (array) {
                        delete[] array;
                        array = 0;
                }
        }
        array_1d &operator[](size_t index) { return array[index]; }
};

int main(int argc, char **argv)
{
        array_2d<int> arr = new array_2d<int>;
        arr.init(16, 8);

        arr[8][2] = 18;
        printf("%d\n",
                arr[8][2]
                );

        return 0;
}

Effo UPD: a response to "Isn't that an array of pointers to arrays?", adding the example of array of pointers, very simple:

int main(int argc, char **argv)
{
        array_2d<int*> parr = new array_2d<int*>;
        int i = 10;
        parr.init(16, 8);
        parr[10][5] = &i;
        printf("%p %d\n",
                parr[10][5],
                parr[10][5][0]
                );

        return 0;
}

Did I still misunderstand your question?

And you could even

typedef array_2d<int*> cell_type;
typedef array_2d<cell_type*> array_type;

int main(int argc, char **argv)
{
    array_type parr = new array_type;
    parr.init(16, 8);
    parr[10][5] = new cell_type;
    cell_type *cell = parr[10][5];
    cell->init(8, 16);
    int i = 10;
    (*cell)[2][2] = &i;

    printf("%p %d\n",
            (*cell)[2][2],
            (*cell)[2][2][0]
            );

    delete cell;

    return 0;
}

It also works on my FC9 x86_64 system.

Test
  • 1,697
  • 1
  • 11
  • 10
  • @Effo: This implementation has some serious bugs. Assignment operator missing. Possible double delete of pointers if used incorrectly. array_1 can be accidentally copied to a local copy and on desruction will delete a row in the array. – Martin York Nov 20 '09 at 07:21
  • @Martin York: right. I just write it to 1. present my ideas; 2. make sure the ideas will being work. – Test Nov 20 '09 at 09:04
0

If you need the 2D array to be dynamic in size (i.e. dimensions known at runtime) you can use a specialized library for that. Like Boost.MultiArray (other answer), or Multi:

#include <multi/array.hpp>

namespace multi = boost::multi;

int main() { 

    multi::array<double*, 2> A({3, 4}, nullptr);

    for(auto& e : A.elements()) {
        e = new double(99.);
    }

    assert( *A[1][2] == 99. );

    auto B = A;  // this will copy the pointers

    for(auto& e : B.elements()) {
        delete e;
    }
}

https://godbolt.org/z/G6WrP9vKW

alfC
  • 14,261
  • 4
  • 67
  • 118