I am working on a very simple 3D vector class, which I can use to set up and access three-dimensional vectors using a wrapper around std::vector. It's not intended to be foolproof, just good enough to get by.
The main goal is to make it simple to use, explicit and easy to debug. I have often accidentally put in out-of-bounds coordinates or given invalid dimensions, so this at least can catch those cases and report back in an understandable way.
Hence all the debug prints, should it be used improperly for any reason.
The only thing I am unhappy with is resize
. I would not have included it (too dangerous), but found some cases in my code where I could not set up its dimensions in advance, like if it was a member of another class. In those cases, it needed a default and resize option.
Since resize does all the checking, I just reused it for the constructor rather than copy-paste all that.
I'm still somewhat new to C++ and was taught from a C background, so I'm always looking to improve my style.
template <typename T>
class Vector3D
{
private:
std::vector<T> data;
public:
// Dimensions in each direction
unsigned long xDim;
unsigned long yDim;
unsigned long zDim;
// Constructor
Vector3D(unsigned long _xDim, unsigned long _yDim, unsigned long _zDim)
{
resize(_xDim, _yDim, _zDim);
}
T& at(unsigned long _x, unsigned long _y, unsigned long _z)
{
if (_x >= xDim || _y >= yDim || _z >= zDim)
{
std::cerr << "Position: " << x << ", " << y << ", " << z << std::endl;
std::cerr << "Vector width: " << xDim << ", " << yDim << ", " << zDim
<< std::endl;
std::cerr << "Invalid indices into 3D vector" << std::endl;
// Throw an exception here..
}
unsigned position = _x + (_y * xDim) + (_z * xDim * yDim);
return data[position];
}
/**
* @brief Default constructor. Dangerous to use, should use the explicit one
* if at all possible.
*/
Vector3D()
{
xDim = 0;
yDim = 0;
zDim = 0;
}
/**
* @brief Re-sizes a 3D vector with new dimensions.
* @details This is a nuclear option. You're re-sizing the dimensions of a
* three-dimensional vector. Do not expect results to be good. Only use this
* explicitly when initialization is impossible up-front.
* @param _xDim, _yDim, _zDim - The new dimensions of the vector
*/
void resize(unsigned _xDim, unsigned _yDim, unsigned _zDim)
{
if (_xDim == 0 || _yDim == 0 || _zDim == 0)
{
// Throw an exception here...
}
xDim = _xDim;
yDim = _yDim;
zDim = _zDim;
// Resize could fail, especially if the dimensions are too large. Just print
// out the dimensions in x,y,z, number of elements and total bytes just in
// case
try
{
data.resize(xDim * yDim * zDim);
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
std::cerr << "Error allocating 3D Vector. Dimensions: ";
std::cerr << "X: " << xDim << " Y: " << yDim << " Z: " << zDim
<< std::endl;
std::cerr << "Number of elements: " << (xDim * yDim * zDim)
<< std::endl;
std::cerr << "Size in bytes: " << (xDim * yDim * zDim) * sizeof(T)
<< std::endl;
// Throw an exception here...
}
}
unsigned long getSize() { return data.size(); }
// Should only be used if the vector is storing pointers to some data
void freeData()
{
for (unsigned long i = 0; i < data.size(); i++)
{
free(data[i]);
}
}
// A couple methods to get the real vector, if they want it.
std::vector<T>& getData() { return data; }
const std::vector<T>& getData() const { return data; }
};