C++ 2D generic array class

Recently I found myself writing a templated 2d array class in C++ for a personal project, and I thought I’d share. If you want to learn about 2d arrays or templates in C++, this article should provide a little insight for you. On the other hand, if you’re an experienced C++ developer, you should be able to slam out a class just like mine in minutes, but the code is at the end so you don’t have to.

Yes, you’re welcome to use this code or expand on it for your own project. It’s available under a BSD license which should be appropriate for most projects.

Usage
The Array2D class can be used like so:

// Create a 3x5 array of longs and fill it with zeroes.
Array2D<long> myArray( 3, 5 );
myArray.setAll( 0 );

// Now we're going to set the 0, 0 element to 23 and then retrieve it.
myArray.set( 23, 0, 0 );
long twentyThree = myArray.get( 0, 0 );

Make sense? Let’s walk through a few key parts.

Template
Back in the old days, you’d have to write your own list class for each data type you wanted to put in a list. For example you might have ListInt and ListString to store ints and strings, respectively. But nowdays you can just have one generic List class that stores any data type.

In C++ the Standard Template Library classes take care of a lot of data types for you. For lists we typically use the vector class and instantiate it with vector<int> for a list integers and vector<string> for a list of strings. It’s a real time saver. Yay for lazy programmers!

We make our Array2D class generic by using a C++ template. Here’s how it works.

template <class T>
class Array2D
{

This is what tells the compiler than you have to instantiate the class with a specific type, for example MyClass; instead of just MyClass;. We’re calling our data type T. Now how do we use it? Here’s how:

Data storage
The data is stored as a double pointer of type T. This double pointer mechanism seemskind of clumsy, but it’s the only way to create a 2D array in C++.

T** data;

Our data is actually stored as [y][x] because that felt more comfortable to me. It’s just a convention I happen to like, I swear I’m not dyslexic.

Memory management
I found myself copying and pasting allocation, deallocation, etc. code just to use a damn 2D array, which it what inevitably led to the code we have here today.

data = new T*[height];
for ( int i = 0; i < height; i++ )
{
	data[i] = new T[width];
}

This is actually fairly straightforward — we’re creating one array for the height, which contains pointers to array for the width. Pretend you just created y vectors of size x.

Deallocation is pretty much the same, so I won’t go into it here. Basically we just delete everything we created here, but in the opposite order.

Accessing data
How do we change and retrieve data in our class? Easily.

T get( const int& x, const int& y )
{
	return data[y][x];
}

void set( const T& t, const int& x, const int& y )
{
	data[y][x] = t;
}

Internally, the data access looks like you would expect for a 2D array. There’s also methods for retrieving the dimensions so the user can iterate over it.

We only expose a getter and setter, never a pointer to the actual data. Otherwise the user could delete a row and the whole program would come crashing down.

One other note — this class was designed for speed. If you’re wondering what’s up with the paramters like “const int& z” it’s pretty simple. It tells the compiler that instead of making a copy, you can pass a reference to the same data into the function. The const means that we won’t change that value inside the function (we would get a compiler error if we did) so there’s no need to worry. This gives us a little extra speed because pass by reference is a little faster than pass by value, since no copy is made.

Okay! I hope I didn’t lose any C++ beginners there. If you’re lost, play with the code because that’s the best way to learn. Hell, add some more features just for fun:

Features you might want to add:

  • What about a 3D array, a 4D array, etc?
  • Matrix functions: matrix addition, cross product, dot product…
  • Overload the square brackets for easy access
  • Resize funtion

Okay, now on to the source listing!

Array2D.h

// Copyright (c) 2009, Eric Gregory
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//    * Neither the name of Eric Gregory nor the
//      names of its contributors may be used to endorse or promote products
//      derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY ERIC GREGORY ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL ERIC GREGORY  BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#ifndef ARRAY2D_H_
#define ARRAY2D_H_

template <class T>
class Array2D
{
public:

	Array2D()
	{
		data = NULL;
		width = 0;
		height = 0;
	}

	Array2D( const int& width, const int& height )
	{
		data = NULL;
		allocate( width, height );
	}

	~Array2D()
	{
		deallocate();
	}

	T get( const int& x, const int& y )
	{
		return data[y][x];
	}

	void set( const T& t, const int& x, const int& y )
	{
		data[y][x] = t;
	}

	void setAll( const T& t )
	{
		for ( int y = 0; y < height; y++ )
		{
			for ( int x = 0; x < width; x++ )
			{
				data[y][x] = t;
			}
		}
	}

	int getWidth()
	{
		return width;
	}

	int getHeight()
	{
		return height;
	}

private:

	void allocate( const int& width, const int& height )
	{
		// Remember dimensions.
		this->width = width;
		this->height = height;

		// Allocate.
		data = new T*[height];
		for ( int i = 0; i < height; i++ )
		{
			data[i] = new T[width];
		}
	}

	void deallocate()
	{
		if ( NULL == data )
		{
			// Nothing to do.
			return;
		}

		// Free the memory.
		for ( int i = 0; i < height; i++ )
		{
			delete[] data[i];
		}
		delete[] data;

		// Reset.
		width = 0;
		height = 0;
		data = NULL;
	}

	int width;
	int height;
	T** data;
};

#endif /* ARRAY2D_H_ */