Part 5 of C++ tutorial – a 3D vector & transform library
What remains to implement for the Vector class is rather ancillary, i.e. constructors and conversions, element access, and informational functions.
The Vector is represented by three doubles: x,y,z. However, it is also an array of three doubles, i.e. double [3]
. Although the Microsoft C++ compiler permits anonymous structs which would allow the use of a union to enable the superimposition of double x,y,z
with double[3]
, for portability, I will use casts and conversion operators to implement this. The array is simply the address of the first element, i.e. &x
. Accessing elements can be done by overloading the array element operator, which permits bounds checking. The helper methods are thus:
double* array() { return &x; }
double& element(int i)
{ switch (i)
{ case 0: return x; case 1: return y; case 2: return z;
default: throw std::out_of_range("Element index");
}
}
These are also implemented in const form, but note that the const element method returns a const reference rather than a value – being an accessor rather than a function returning a value.
These become public operators of the Vector class as follows:
explicit operator const double* () const { return array(); }
explicit operator double* () { return array(); }
const double& operator[](int i) const { return element(i); }
double& operator[](int i) { return element(i); }
Array conversion operators must be made explicit, otherwise invalid vector arithmetic expressions are liable to be converted into pointer arithemtic expressions. Otherwise, a Vector is just as much an array as it is x,y,z.
Another point on arrays to note is that C++ does not encode the size of the array when matching arguments, i.e. an array of 4 doubles will match an array of 3. Thus the following constructor (from an array of 3 doubles) will take any sized array or pointer-to-double.
explicit Vector(const double p[3]):x(p[0]),y(p[1]),z(p[2]) { }
Thus, it should be explicit to improve readability and safety, e.g. an array of three doubles (or object with an implicit conversion to such) is not necessarily a vector.
There is a special vector, which is the null vector. There are thus a few members concerned with this:
bool is_null() const { return !x&&!y&&!z; }
explicit operator bool() const { return !is_null(); } // Don't want Vector implicitly converted to int
bool operator!() const { return is_null(); }
static const Vector null;
The ‘null’ member is initialised as const Vector Vector::null=Vector(0,0,0);
and may sometimes be useful (referenced via Vector::null).
I have implemented an explicit cast of a vector to a double, by way of providing a means to obtaining the vector’s magnitude. I would argue that this is a valid, and unsurprising explicit conversion of a vector to a scalar.
For other methods such as equality comparison, I leave you to locate these in the code below:
#include <iostream> // For console output functionality
using namespace std; // Avoids having to use std:: scoping prefix
class Vector // A vector class with addition and subtraction functionality
{
protected:
// Accessor helpers
const double* array() const { return &x; }
double* array() { return &x; }
const double& element(int i) const // Const array element accessor
{ switch (i)
{ case 0: return x; case 1: return y; case 2: return z;
default: throw std::out_of_range("Element index");
}
}
double& element(int i) // Non-const array element accessor - for assignment
{ switch (i)
{ case 0: return x; case 1: return y; case 2: return z;
default: throw std::out_of_range("Element index");
}
}
double* read(double v[3]) const { v[0]=x; v[1]=y; v[2]=z; return v; } // Copies this vector to supplied array
// Non-modifying helpers
bool is_null() const { return !x&&!y&&!z; }
bool is_equal_to(const Vector& v) const { return x==v.x&&y==v.y&&z==v.z; }
double magnitude_squared() const { return x*x+y*y+z*z; }
double magnitude() const { return sqrt(magnitude_squared()); }
Vector negation() const { return Vector(-x,-y,-z); }
Vector normalisation() const { return division(magnitude()); }
Vector addition(const Vector& v) const { return Vector(x+v.x,y+v.y,z+v.z); }
Vector subtraction(const Vector& v) const { return Vector(x-v.x,y-v.y,z-v.z); }
Vector multiplication(double m) const { return Vector(x*m,y*m,z*m); }
Vector division(double d) const { if (d) return Vector(x/d,y/d,z/d); else throw std::runtime_error("Divide by Zero"); }
double dot_product(const Vector& v) const { return v.x*x+v.y*y+v.z*z; }
Vector cross_product(const Vector& v) const { return Vector(v.y*y-v.y*z,v.x*z-v.y*x,v.y*x-v.x*y); }
// Modifying helpers
void assign(const Vector& v) { x=v.x; y=v.y; z=v.z; }
void add(const Vector& v) { assign(addition(v)); }
void subtract(const Vector& v) { assign(subtraction(v)); }
void multiply_by(double m) { assign(multiplication(m)); }
void divide_by(double d) { assign(division(d)); }
public:
// Elements and accessors
double x,y,z; // Representation
explicit operator const double* () const { return array(); } // Cast to const array
explicit operator double* () { return array(); } // Cast to non-const array
const double& operator[](int i) const { return element(i); } // Const array element accessor
double& operator[](int i) { return element(i); } // Non-const array element accessor - for assignment
double* Read(double p[3]) const { return read(p); }
// Constructors
Vector(double a,double b,double c):x(a),y(b),z(c) { }
explicit Vector(const double p[3]):x(p[0]),y(p[1]),z(p[2]) { }
static const Vector null;
// Assignment
Vector& operator=(const Vector& v) { assign(v); return *this; }
// Informational
explicit operator bool() const { return !is_null(); } // Don't want Vector implicitly converted to int or bool
bool operator!() const { return is_null(); }
explicit operator double() const { return magnitude(); }
bool operator==(const Vector& v) const { return is_equal_to(v); }
bool operator!=(const Vector& v) const { return !is_equal_to(v); }
// Scalar operations
Vector operator-() const { return Vector(negation()); }
Vector& operator*=(double m) { multiply_by(m); return *this; }
Vector& operator/=(double d) { divide_by(d); return *this; } // Throws div0
friend Vector operator*(const Vector& v,double m) { return Vector(v.multiplication(m)); }
friend Vector operator*(double m,const Vector& v) { return Vector(v.multiplication(m)); }
friend Vector operator/(const Vector& v,double d) { return Vector(v.division(d)); }
// Vector operations
Vector& operator+=(const Vector& v) { add(v); return *this; }
Vector& operator-=(const Vector& v) { subtract(v); return *this; }
friend Vector operator+(const Vector& u,const Vector& v) { return Vector(u.addition(v)); }
friend Vector operator-(const Vector& u,const Vector& v) { return Vector(u.subtraction(v)); }
double dot(const Vector& v) const { return dot_product(v); }
Vector cross(const Vector& v) const { return cross_product(v); }
// Output
friend ostream& operator<<(ostream& os,const Vector& v)
{ return os<<'('<<v.x<<','<<v.y<<','<<v.z<<')'; } // Output, e.g. (1,2,3)
};
const Vector Vector::null=Vector(0,0,0); // A standard vector
int main() // The program
{ const Vector u(1,2,3); // A test vector
double d[4]={6,4,3,1};
Vector v(d); // A test vector
cout<<"u="<<u<<"\n";
cout<<"v="<<v<<"\n";
double t0=v[0],t1=*(v+1);
cout<<"t0,t1="<<t0<<","<<t1<<"\n";
Vector v0=Vector::null;
cout<<"v0="<<v0<<"\n";
if (v0)
cout<<"v0 is true\n";
else
if (!v0)
cout<<"v0 is false\n";
else
cout<<"v0 is neither true nor false\n";
return 0;
}
Program output is:
u=(1,2,3)
v=(6,4,3)
t0,t1=6,4
v0=(0,0,0)
v0 is false
Comments
There are currently no comments on this article.
Comment