18
\$\begingroup\$

I'm creating a couple of classes Vector2 (X & Y) and Vector3 (X, Y & Z), but I don't know whether to make Vector3 inherit from Vector2, or whether to re-implement the member variables m_x and m_y again? What are the pros and cons of each side (inheritance vs redefinition).

Edit: I'm using C++ (VS2010).

\$\endgroup\$
15
  • 1
    \$\begingroup\$ Why don't write a general vector class for n dimensional vectors and then (if needed) inherit a vector2 and a vector3 class. You may also use templates for the general class and inherit versions for integer vectors and float vectors, too. Edit: Why don't you use a optimized math library? \$\endgroup\$
    – danijar
    Oct 16, 2012 at 21:07
  • 5
    \$\begingroup\$ By no stretch of the imagination "Vector3 is a Vector2", they could both inherit from a parent VectorN though \$\endgroup\$
    – wim
    Oct 17, 2012 at 0:00
  • 1
    \$\begingroup\$ Yeah, but then you'll probably need a virtual table and that's one of the cases where the runtime & memory costs can matter. Ideally, a Vector3 should be just 3 floats as far as the memory is concerned. Not saying that's impossible, just that I've never seen that in a production engine. \$\endgroup\$ Oct 17, 2012 at 8:12
  • 2
    \$\begingroup\$ Yes, I do think so. Until as long as you don't need anything else that floats. You know, YAGNI, KISS, all that stuff. Vector2, Vector3 and Vector4 with no inheritance and floats only is really the de facto standard in game engines. \$\endgroup\$ Oct 17, 2012 at 9:19
  • 1
    \$\begingroup\$ I hope you meant typedef float real; ;). \$\endgroup\$ Oct 17, 2012 at 15:46

6 Answers 6

47
\$\begingroup\$

No it shouldn't. The only thing you'd be using from the inheritance is the x and y components. The methods used in a Vector2 class wouldn't be useful in a Vector3 class, they would likely take different arguments and perform operations on a different number of member variables.

\$\endgroup\$
3
  • \$\begingroup\$ +1, I should pay more attention to the popup so I don't write redundant stuff. \$\endgroup\$
    – Matsemann
    Oct 16, 2012 at 18:55
  • 8
    \$\begingroup\$ Classical inheritance overuse. A Vector3 IS-NOT-A Vector2 (so it should not inherit), but an Apple IS-A Fruit (so it may inherit). If you twist your mind enough, a Vector3 HAS-A Vector2 in it, but the performance loss and difficulty coding means you will write completely separate classes for Vector3 and Vector2. \$\endgroup\$
    – bobobobo
    Oct 17, 2012 at 15:20
  • \$\begingroup\$ But you could (an in my opinion should) write a n-dimensional vector class to inherit a 2d vector and a 3d vector from that. \$\endgroup\$
    – danijar
    Nov 11, 2012 at 18:21
8
\$\begingroup\$

There's a curious thing you can do with C++ (You didn't specify a language, and this answer is mostly because I think it's nice to see alternatives, though I don't really believe this is useful in most cases.)

Using templates you can do something like this:

template <class T, class S, int U>
class VectorN
{
    protected:
        int _vec[U];
    public:
        S& operator+=(const S c)
        {
            for(int i = 0; i < U; i++)
            {
                _vec[i] += c.at(i);
            }
            return (S&)*this;
        }
        int at(int n) const
        {
            return _vec[n];
        }
};

template <class T>
class Vec2 : public VectorN<T,Vec2<T>,2>
{
    public:
        T& x;
        T& y;
        Vec2(T a, T b) : x(this->_vec[0]), y(this->_vec[1])
        {
            this->_vec[0] = a;
            this->_vec[1] = b;
        }
};

template <class T>
class Vec3 : public VectorN<T,Vec3<T>,3>
{
    public:
        T& x;
        T& y;
        T& z;
        Vec3(T a, T b, T c) : x(this->_vec[0]), y(this->_vec[1]), z(this->_vec[2])
        {
            this->_vec[0] = a;
            this->_vec[1] = b;
            this->_vec[2] = c;
        }
};

and this can be used like this:

int main(int argc, char* argv[])
{

    Vec2<int> v1(5,0);
    Vec2<int> v2(10,1);

    std::cout<<((v1+=v2)+=v2).x;
    return 0;
}

Like I said, I don't think this is useful, and it will probably complicate your life when you try to implement dot/normalize/other stuff and try to be generic with any number of vectors.

\$\endgroup\$
5
  • \$\begingroup\$ Yeah, all the genericness seems nice, only most of the time you only need a standard 3-vector with 3 floating point components -- all the angle brackets will make Vector3f v quite a bit more bloaty Vector3<float> v \$\endgroup\$
    – bobobobo
    Oct 17, 2012 at 14:55
  • \$\begingroup\$ @bobobobo Yes, I agree. My vector classes are usually vec2 and vec3 with no parent, but still make them templates. If writing Vector3<float> bothers you, you can always typedef it \$\endgroup\$
    – Luke B.
    Oct 17, 2012 at 15:11
  • \$\begingroup\$ ..And now the C programmer's argument.. "but what about increased compile time for using templates??" Is it really worth it in this case? \$\endgroup\$
    – bobobobo
    Oct 17, 2012 at 15:17
  • \$\begingroup\$ @bobobobo I never had any problem with my compile times :P, but I never worked on a project that compile time would be an issue. One could argue that the compile times justify not using floats when you need integers. \$\endgroup\$
    – Luke B.
    Oct 17, 2012 at 15:24
  • \$\begingroup\$ @bobobobo With explicit instantiations and not including your inline file in the header, compile times will be no different. Additionally, the template angle bracket bloat is just one typedef away. \$\endgroup\$
    – Samaursa
    Dec 26, 2012 at 13:43
7
\$\begingroup\$

Regardless of speed, the first question you should ask yourself when doing any inheritance is if you're going to be using them polymorphically. More specifically, is there any situation where you can see yourself using a Vector3 as if it were a Vector2 (which, by inheriting from it, you're explicitly saying that a Vector3 "is-a" Vector2).

If not, then you shouldn't use inheritance. You shouldn't be using inheritance to share code. That's what components and external functions are for, not that you'd be sharing any code between them anyway.

That being said, you might want easy ways to convert Vector3s to Vector2s, and in that case you can write an operator overload that will implicitly truncate the Vector3 to a Vector2. But you shouldn't inherit.

\$\endgroup\$
4
  • \$\begingroup\$ Thanks, I think that's highlighted the problem, I was looking at this from a "code sharing" point of view (i.e. not having to "re-type" the X & Y values). \$\endgroup\$ Oct 17, 2012 at 15:05
  • \$\begingroup\$ +1 great answer, there's no polymorphic use between vectors of different sizes. \$\endgroup\$
    – Luke B.
    Oct 17, 2012 at 15:26
  • \$\begingroup\$ This is the biggest thing I was going to add to my own answer - +1 for sure. (Although there are strange circumstances in which I can imagine wanting polymorphism - e.g., 2.5d 'heightmap' games where things like distance checks, pathing etc. will canonically want to be done in 2d but you still need to provide 3d coordinates for objects) \$\endgroup\$ Oct 17, 2012 at 21:30
  • \$\begingroup\$ @LukeB. Although in OPs case I agree that it seems there is no reason to inherit from Vector2 but inheriting from a base Vector<N>? That makes perfect sense. Moreover, why does inheritance automatically mean polymorphic behaviour? One of the best things about C++ is that you can have zero cost inheritance. No need to add any virtual methods (including virtual destructors) in the base Vector<N> class. \$\endgroup\$
    – Samaursa
    Dec 26, 2012 at 13:46
5
\$\begingroup\$

No, since every method will need to be overridden as well you will have no use of actually inheriting from it.

If something, they could both implement a Vector interface. However, since you probably don't want to add/sub/dot/dst between a Vector2 and Vector3 this will have unwanted side effects. And having different parameters etc. would be a hassle.
So I really cannot see any pros of inheritance/interface in this case.

An example is the Libgdx framework, where Vector2 and Vector3 have nothing to do with each other, other than having same type of methods.

\$\endgroup\$
0
2
\$\begingroup\$

If you plan to use SIMD arrays are likely the best. If you still wish to use operator overloading you can consider using an interface/mixin to access the underlying array - for example, here is a starting point that has only the (untested) Add.

Notice how I haven't provided X/Y/Z, each VectorX class would inherit directly from this one - for the same reasons specified by other people. Still, I have seen arrays used as vectors many times in the wild.

#include <xmmintrin.h>

class Vector
{
public:
    Vector(void)
    {
        Values = AllocArray();
    }

    virtual ~Vector(void) 
    { 
        _aligned_free(Values);
    }

    // Gets a pointer to the array that contains the vector.
    float* GetVector()
    {
        return Values;
    }

    // Gets the number of dimensions contained by the vector.
    virtual char GetDimensions() = 0;

    // An example of how the Vector2 Add would look.
    Vector2 operator+ (const Vector2& other)
    {
        return Vector2(Add(other.Values));
    }

protected:
    Vector(float* values)
    {
        // Assume it was created correctly.
        Values = values;
    }

    // The array of values in the vector.
    float* Values;

    // Adds another vector to this one (this + other)
    float* Add(float* other)
    {
        float* r = AllocArray();

#if SSE
        __m128 pv1 = _mm_load_ps(Values);
        __m128 pv2 = _mm_load_ps(other);
        __m128 pvr = _mm_load_ps(r);

        pvr = _mm_add_ps(pv1, pv2);
        _mm_store_ps(r, pvr);

#else
        char dims = GetDimensions();
        for(char i = 0; i < dims; i++)
            r[i] = Values[i] + other[i];
#endif

        return r;
    }

private:

    float* AllocArray()
    {
        // SSE float arrays need to be 16-byte aligned.
        return (float*) _aligned_malloc(GetDimensions() * sizeof(float), 16);
    }
};

Disclaimer: My C++ might suck, it's been a while since I used it.

\$\endgroup\$
4
  • \$\begingroup\$ Wait man, does your use of _aligned_malloc mean the bug I opened isn't really a bug? \$\endgroup\$
    – bobobobo
    Oct 17, 2012 at 14:58
  • \$\begingroup\$ You shouldn't use pointer casts to get your values into the __m128 register, you should use _mm_loadu_ps instead. A good sample class is here under "vectorclass.zip" \$\endgroup\$
    – bobobobo
    Oct 17, 2012 at 15:02
  • \$\begingroup\$ @bobobobo I'll make a best-attempt at an edit - take special heed to the disclaimer ;). \$\endgroup\$ Oct 18, 2012 at 16:40
  • \$\begingroup\$ @bobobobo _mm_loadu_ps should work out for you with that struct (where _mm_load_ps won't). I also added your suggestion - feel free to edit the question if you feel that I am barking up the wrong tree (it has been a while since I have used C[++]). \$\endgroup\$ Oct 18, 2012 at 16:50
1
\$\begingroup\$

Another serious con to having Vec3 inherit from Vec2 or, arguably, to having both inherit from a single Vector class: your code will be doing a lot of operations on vectors, often in time-critical situations, and it's very much in your best interests to make sure that all of those operations are as fast as they can be - much more so than it is for many other objects that aren't quite so universal or low-level. While a good compiler will do its best to flatten out any inheritance overhead, you're still relying more on the compiler there than you'd like to; instead, I would build them as structs with as little overhead as possible and possibly even try and make most of the functions that use them (with the exception of things like operator+ which can't really be helped) be globals rather than methods on the struct. Early optimization is generally recommended against, and with excellent reason, but in a circumstance like this where you can be sure that vector operations will almost certainly be a substantial chunk of your runtime, it's worth a bit of effort up front to design for efficiency.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ -1 because: class and struct only have access grant implications in C++ and the OP didn't specify a language anyhow, non-virtual member functions have the same performance implications as non-member functions, and virtual member functions (which do potentially exhibit the issues you're concerned about) only exist if you make them, not simply by utilizing inheritance. \$\endgroup\$
    – user1430
    Oct 16, 2012 at 19:42
  • 2
    \$\begingroup\$ @JoshPetrie Valid points on all fronts; I tend to 'default' to C/C++ and so saw the question through that lens. I do believe there are legitimate performance (as well as conceptual) reasons for not going the inheritance route, mind you, but I could have been much better on the specific details. I'll try and revisit this and see if I can give a better accounting. \$\endgroup\$ Oct 16, 2012 at 20:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .