Right now I have no knowledge of templates, but I just finished learning about inheritance, and wanted to apply it to a Vector3
class that I had already created. My thoughts were that vectors of different dimensions share some common operations, and so it should be possible to apply an inheritance hierarchy to this.
Ultimately I might want a program that can operate with vectors without knowing their dimension. As a result, all vectors inherit from an abstract base class Vector
which contains the most general operations of vectors for all dimensions, such as normalise()
, get_length()
a bool
conversion and <<
>>
operators.
I've included just the header because I'm really concerned only with making sure my inheritance structure is adequate. The function definitions would take up a lot of room, and I don't think they are too important (although let me know if they are necessary).
Vector.h
#ifndef MATHSVectors
#define MATHSVectors
#include <iostream>
#include <utility> //pair
class Vector{ //abstract base class but we do expect users, so virtual functions are public
friend std::ostream& operator<<(std::ostream&, const Vector&);
friend std::istream& operator>>(std::istream&, Vector&);
protected: //needed by derived classes
Vector() { }
Vector(const Vector&) = default;
Vector(Vector&&) = default;
Vector& operator=(const Vector&) = default;
Vector& operator=(Vector&&) = default;
public:
static const double pi;
virtual double get_length() const = 0;
virtual Vector& normalise() = 0;
virtual explicit operator bool() const = 0;
virtual ~Vector() { } //needed if we dynamically allocate
private: //used for operator<< and operator>>
virtual std::ostream& print(std::ostream&) const = 0;
virtual std::istream& read(std::istream&) = 0;
};
std::ostream& operator<<(std::ostream&, const Vector&);
std::istream& operator>>(std::istream&, Vector&);
class Vector2 : public Vector {
friend bool operator==(const Vector2&, const Vector2&);
friend bool operator!=(const Vector2&, const Vector2&);
friend Vector2 operator+(const Vector2&, const Vector2&);
friend Vector2 operator-(const Vector2&, const Vector2&);
friend Vector2 operator*(const Vector2&, double);
friend Vector2 operator*(double, const Vector2&);
friend Vector2 operator/(const Vector2&, double);
friend double dot_product(const Vector2&, const Vector2&);
public:
Vector2() = default;
Vector2(double a, double b): x(a), y(b) { }
explicit operator bool() const override;
Vector2& operator+=(const Vector2&);
Vector2& operator-=(const Vector2&);
Vector2& operator*=(double);
Vector2& operator/=(double);
double get_length() const override;
Vector2& normalise() override;
Vector2& rotateXY(double); //radians
Vector2& setX(double a) { x = a; return *this;}
Vector2& setY(double b) { y = b; return *this;}
double getX() const { return x;}
double getY() const { return y;}
private:
std::ostream& print(std::ostream&) const override;
std::istream& read(std::istream&) override;
std::pair<double, double> rotate(double, double, double);
double x = 0, y = 0;
};
bool operator==(const Vector2&, const Vector2&);
bool operator!=(const Vector2&, const Vector2&);
Vector2 operator+(const Vector2&, const Vector2&);
Vector2 operator-(const Vector2&, const Vector2&);
Vector2 operator*(const Vector2&, double);
Vector2 operator*(double, const Vector2&);
Vector2 operator/(const Vector2&, double);
double dot_product(const Vector2&, const Vector2&);
class Vector3 : public Vector {
friend bool operator==(const Vector3&, const Vector3&);
friend bool operator!=(const Vector3&, const Vector3&);
friend Vector3 operator+(const Vector3&, const Vector3&);
friend Vector3 operator-(const Vector3&, const Vector3&);
friend Vector3 operator*(const Vector3&, double);
friend Vector3 operator*(double, const Vector3&);
friend Vector3 operator/(const Vector3&, double);
friend double dot_product(const Vector3&, const Vector3&);
friend Vector3 cross_product(const Vector3&, const Vector3&);
public:
Vector3() = default;
Vector3(double a, double b, double c): x(a), y(b), z(c) { }
explicit operator bool() const override;
Vector3& operator+=(const Vector3&);
Vector3& operator-=(const Vector3&);
Vector3& operator*=(double);
Vector3& operator/=(double);
double get_length() const override;
Vector3& normalise() override;
Vector3& rotateXY(double); //radians
Vector3& rotateXZ(double);
Vector3& rotateYZ(double);
Vector3& setX(double a) { x = a; return *this;}
Vector3& setY(double b) { y = b; return *this;}
Vector3& setZ(double c) { z = c; return *this;}
double getX() const { return x;}
double getY() const { return y;}
double getZ() const { return z;}
private:
std::ostream& print(std::ostream&) const override;
std::istream& read(std::istream&) override;
std::pair<double, double> rotate(double, double, double);
double x = 0, y = 0, z = 0;
};
bool operator==(const Vector3&, const Vector3&);
bool operator!=(const Vector3&, const Vector3&);
Vector3 operator+(const Vector3&, const Vector3&);
Vector3 operator-(const Vector3&, const Vector3&);
Vector3 operator*(const Vector3&, double);
Vector3 operator*(double, const Vector3&);
Vector3 operator/(const Vector3&, double);
double dot_product(const Vector3&, const Vector3&);
Vector3 cross_product(const Vector3&, const Vector3&);
#endif // Vector3
The one thing I'm concerned with is there might be a lot of redundancy in function definitions - e.g. of defining getX()
or getY()
separately in every class of every dimension. An idea might be to define Vector
and then inherit Vector2
from that, and inherit Vector3
from Vector2
, and so on, so that I have Vector(N+1)
inheriting from Vector(N)
.
What I would be concerned with if using this method is that it reflects an odd relationship. Why would a Vector3
be used in place of a Vector2
? - It would also mean I could compare, or add, a Vector3
to a Vector2
(through run-time binding) and expect a return value of a Vector2
. I am not sure, to be consistent mathematically, whether I would want that.
One last note is that, since I want to refactor the most general operations of a vector in to the Vector
class, I thought it would make sense to refactor arithmetic operators in to the Vector
class. That left me with the issue (again) that I could add Vectors of different dimensions through derived-to-base conversions. The plain arithmetic operators would also return Vector
objects, rather than references, so there could be no consistent virtuality.
I'm sure that once I learn about templates there might be an easier way to generalise a Vector
function to n
dimensions, but for now, is this looking okay?