Assume I have a base class
class Vector
{
public:
Vector(const std::vector<float> &elements);
static Vector add(const Vector &v1, const Vector &v2);
Vector Vector::operator+(const Vector &v) const
{
return add(*this, v);
}
Vector &Vector::operator+=(const Vector &v)
{
*this = add(*this, v);
return *this;
}
float operator[](size_t index) const;
private:
std::vector<float> _elements;
}
And a derived class for a 3 element vector:
class Vector3 : public Vector
{
public:
Vector3(float x, float y, float z);
};
What would I have to do to use the operators with the derived class?
My solution was to redefine the operators in the derived class like so:
Vector3 Vector3::operator+(const Vector3 &v) const
{
Vector res = Vector::operator+(v);
return Vector3(res[0], res[1], res[2]);
}
Vector3 &Vector3::operator+=(const Vector3 &v)
{
Vector res = Vector::operator+=(v);
*this = Vector3(res[0], res[1], res[2]);
return *this;
}
That works so far, but I was wondering if this is the right way to go. For me it looks a lot like double work. Furthermore I don't fully understand why I even have to redefine the operators in the first place. Is it because I can't cast from Vector
to Vector3
? My simple assumption was Vector3
is the same as Vector
with size 3, so in my mind there isn't a difference that would be relevant for the given operator task to be different. If someone could enlighten me whether there is a better approach that would be great.
Frame challenge: What you want to do here adds complexity, inheritance and the hoops (practical and conceptual) you have to jump through because of it, and reduces performance1. That's the kiss of death trade-off. Since you generally only want to suffer increased complexity in order to get increased performance, do something else.
My suggestion, keeping most of what you had in mind is to
Vector
into a template, with the size being a template argument (and potentially the data type stored so you can easily switch between float
s, double
s, and int
s)Vector3
into a typedef. eg using Vector3 = Vector<3>;
. Now Vector3
literaly is a Vector
so there is no need for wresting with inheritance while providing exactly the behaviour you want.Here is a quick demo:
#include <array>
#include <cstddef>
#include <functional>
#include <algorithm>
#include <iostream>
template<size_t SIZE> // A more general <class TYPE, int SIZE> will be more useful
class Vector
{
public:
// initializer_list may make this easier to work with
Vector(const std::array<float,SIZE> &elements): _elements(elements)
{
}
Vector & operator+=(const Vector &v)
{
//just crapping out a simple summation here for demo purposes
std::transform(_elements.begin(), _elements.end(),
v._elements.begin(),
_elements.begin(),
std::plus());
return *this;
}
float operator[](size_t index) const;
// consider adding the usual iterator -granting functions so
// you can use one of these in Standard algorithms
// added for demo purposes to print out the result
friend std::ostream & operator<<(std::ostream & out,
const Vector & vec)
{
for (const auto & val:vec._elements)
{
out << val << ' ';
}
return out;
}
private:
std::array<float, SIZE> _elements; // Look ma! No pointers!
// The Vector data is IN the Vector!
// no extra look-ups and no extra
// cache misses
};
// now a free function to aid in decoupling
template <size_t SIZE>
Vector<SIZE> operator+(Vector<SIZE> lhs,
const Vector<SIZE> &rhs)
{
lhs += rhs;
return lhs;
}
using Vector3 = Vector<3>;
int main()
{
Vector3 a({1,2,3});
Vector3 b({3,2,1});
Vector3 c = a+b;
std::cout << c << '\n';
Vector<4> d({});
//Vector3 e = c+d; // addition fails at compile time because the types
// don't match. Much debug savings
}
1 std::vector
points at its data, so the CPU almost always has to read the Vector
and the vector
data separately.
For a small amount of data that could have been read in one shot, this can be an utterly murderous performance hit.