Introduction
The header file attached to this article implements the EVector
class. In order to remove confusion from the start, this class is not a data structure, or an STL container. It does, however, resemble the std::vector
class that's provided as part of STL even though their purposes are quite different.
std::vector
is basically a container for objects of type T
. This class, however, is an implementation of a vector in the mathematical sense. Therefore, it contains operators like +
, -
, *
, etc.. as well as other functions that are used to manipulate vectors.
Background
This class was written as part of a library I've been writing. I wanted to create some mathematical infrastructure that will be used by other parts of the library. Up till now, I've used this class to write a matrix class and to specialize the case of 2D and 3D vectors for graphics and computational geometry algorithms. Even though I haven't provided those classes (I might in the future, though...) I think it's very easy to use/extend/specialize this class for such purposes by any user.
Implementation
While writing this class, I followed these guidelines:
- This class is really low-level and will be used almost everywhere. This is why it has to be well written and conform to widely-used notation.
- Future changes will be addition of functionality and not changes in already existing behavior.
- Portability.
- This class should conform to standard behavior in order to be used by other 3rd-party libraries or replace similar classes.
- Other people might use this class and therefore it must be well documented. This will also facilitate changes in the future.
To adhere to the above, the code is documented using Doxygen. In addition, it is STL-compliant. This means that it has an allocator object and appropriate iterators that can be used to traverse the elements of the vector. The elements of the vector are allocated and deallocated using the allocator.
One of the intentions of this class is to be used for graphics and code that is run-time critical. Any of you who have used OpenGL or Direct3D are familiar with vertex arrays. A vertex array is just what its name implies - an array of (usually) 3D vectors. Such arrays can be cached, or optimized, and for such reasons there's no need to impose the use of an allocator instance for each instance of EVector
. In this example, an array of EVector
s can be easily used as a vertex array that can be passed to OpenGL and the programmer doesn't even need to calculate the offset between consecutive instances of EVector
s (even though it's easy...). Such an array will definitely occupy less memory if there's no allocator instance for each EVector
instance. This is why I make use of the E_VECTOR_USE_ALLOCATOR
flag.
If you take a look at the code, you'll see the E_VECTOR_USE_ALLOCATOR
flag. If this flag is defined, EVector
has a template parameter A
that determines the type of the allocator (which is by default std::allocator
). In addition, each instance of an EVector
object will hold an instance of an allocator - just like std::vector
(or any other STL container). If, however, it isn't defined, all instances of EVector
of the same type (meaning, having the same T
and n
) will share the same allocator instance. To make a long story short - E_VECTOR_USE_ALLOCATOR
determines whether the allocator is a static member of EVector
or not. This still allows the flexibility of providing different ways of allocating objects without the overhead of complexity and memory usage that might arise in most cases. It is important to emphasis that whether E_VECTOR_USE_ALLOCATOR
is defined or not, EVector
can be used identically. This is because if E_VECTOR_USE_ALLOCATOR
isn't defined, A
becomes a typedef of the class, and get_allocator()
simply returns the static member.
Unfortunately, I can't get into the skinny math details, so I assume that most users of this class know some linear algebra. It really isn't complicated. As mentioned above, the class contains the standard functions that manipulate vectors. If anything is missing and should be in - let me know. :-)
As I mentioned above, the class is STL-compliant - which allows it to be used by STL algorithms. I've demonstrated this in the small test program for the case of std::reverse
but, of course, it can be used with other algorithms as well. It also includes simple serialization to and from a stream.
An important thing to notice. The size of the vector is static!! It can't be changed at run-time and is determined completely by the template parameter n
. This is in contrast to some other classes I've seen. There are several reasons for this. The main two are simplicity and performance. Like I said, this class is intended to be used for performance-critical tasks. If the size was changeable at run-time, simple operations like addition, subtraction, dot product, and so forth would have to be validated at run-time by checking that vectors have the same size and otherwise throwing an exception. Keeping the size static and part of the template parameters removes all such problems because these are simply verified during compilation. If one tries to define:
EVector< float, 5 > v;
EVector< float, 3 > u;
cout << u+v << endl;
one would get a compilation error because the operators mentioned above are defined for vectors of the same size.
Using the code
This is simple. All you have to do is to include EVector.h in your source code. Then you can define:
EVector< double, 4 > v;
EVector< int, 2 > p;
v.fill(0.1);
p[0] = 1; p[1] = 5;
v.normalize();
std::reverse(p.begin(), p.end());
And start having fun with them...
Please look at the (Doxygen) comments in the code to find out what are the operators and functions that are defined for the class.
Points of Interest
Please notice that the default constructor creates an invalid vector. An invalid vector is a vector one of whose arguments is NaN (Not a Number). To check for the validity of a vector one should use the isValid()
member function.
You'll see that for some of the functions there's an optional epsilon argument. This is provided because some computations (actually, most computations...) result in numerical errors. For example, two vectors are considered orthogonal if their dot product is 0. Since the computer has only finite accuracy, two vectors which would be practically orthogonal might not yield an exact 0 when calculating their dot product. The epsilon argument solves this problem by considering vectors whose dot product is less or equal to epsilon orthogonal, and vectors whose dot product is greater than epsilon - not orthogonal. The same principle applies for other kinds of tests.
History
No history yet...