Skip to main content
GameDev.net gamedev.net
🔒 Locked

3D and 2D Vector Classes

Started by MrPickle Aug 26, 2008 at 8:47 AM 32 replies 4.6k views
Original Post
MrPickle
MrPickle
What functions would a good 2D & 3D vector class have? I plan on making 2 seperate classes for each. Dot, Normalize etc?
Here to learn :)
hymerman
hymerman
Apparently, none: http://www.gamedev.net/community/forums/topic.asp?topic_id=506184

My advice would be to code things as and when you need them, since you never know what will be needed and what will just be a waste of time.
MrPickle
MrPickle
Ok, I'll code them as I need them.
Here to learn :)
oliii
oliii
operators are nice to have on vectors. Just be careful of operator precedence.
Everything is better with Metal.
alvaro
alvaro
You must define at least addition of vectors, subtraction of vectors and multiplication of a vector by a scalar. Anything that doesn't provide those operations doesn't deserve the name "vector".

Numsgil
Numsgil
My advice is to find some vector classes from popular engines (eg: Ogre, Box2D, etc.) and look at their documentation. Ignore the code if you want, just look at all the different methods they have for vectors. Steal any of the methods that you would use. Ignore any of the ones you wouldn't.
[size=2]Darwinbots - [size=2]Artificial life simulation
Ravyne
Ravyne
Quote:
Original post by hymerman
Apparently, none: http://www.gamedev.net/community/forums/topic.asp?topic_id=506184


If that's what you took away from that thread, then you're really misinterpreted what others have said.

A 'class' is two things -- data and interface. The interface is made up of both member and non-member functions, and in fact, non-member functions are usually preferable. Just because a function is defined outside of the class construct doesn't make it any less a part of the class (With the caveat that it must be defined in the same namespace as the class in order for Koenig look-up to be able to find it). I generally preserve member functions for functions which either are not implementable with the public interface (They have to be) or those which modify the class's state (which may have to be in the future, and so, should be now).

Quote:
My advice would be to code things as and when you need them, since you never know what will be needed and what will just be a waste of time.


This is a re-statement of the YAGNI, or You Ain't Gonna Need It, principle. Unfortunately, IMO, some YAGNI proponents take it too far, or oversimplify when extolling its virtues to newbies. Often, a newbie will come away with the impression that YAGNI means to never code any single function until the exact moment its first needed in code. This *can* be of added danger to newbies for a couple of reasons, but the most nefarious of all is that it encourages a newbie coder to think about class design even less than usual (although, on the other side of the coin, its equally likely that a newbie tries to over think class design -- both over and under-thinking are equally bad.)

What YAGNI should be interpreted to mean is that things should be coded when the requirement is determined. This means that, once you have determined the need for a vector class, you should then think carefully about the *requirements* of that class's interface based on the expected needs of the application, rather than the known-needs of of the code you are writing right this instant, or on the maybe-needs of code you might never actually write, or on the later-needs of code you may or may not write 6 months from now.

Taking a vector class as an example, you can determine pretty trivially that any vector class that provides one of the standard mathematical operators for a vector should provide a full compliment of the standard mathematical operators, if for no other reason than the fact that client code ought to be able to reasonably expect, given one of the operators, that all are present. That, along with some basic distance calculation would be enough to satisfy basic 2D gameplay. The next tier up would be to include things like Normalization, the cross (or 2D pseudo-cross) and dot products, and maybe a handful of others to satisfy advanced 2D gameplay and basic-to-intermediate 3D. Beyond that, there's a handful of additional functions that have very specific uses that I've occasionally seen present in a vector class.


Since we're on the topic of class-design, I'd also like to add a bit on whether to overload operators or create named functions. Operators should only be overloaded when they exactly, or at least very closely, model the domain-specific language (be it mathematical notation, regular expressions, BNF, etc) the API is targetting. Further, you must be particularly concerned with behaving as one coming from that background would expect, and with a careful eye towards operator precedence in your language of choice. For example, I've seen a couple C++ vector classes which overload some combination of *, % and ^ to indicate the various products associated with vectors. The problem in using the % and ^ operators is that they have even lower precedence than the + and - operators and can lead to code like this:

a % b + c ^ d // a b c d

which, one would naively assume, would be evaluated as (a%b)+(c^d), but instead is evaluated as a%(b+c)^d. All this is a result of poorly choosing operators to overload. Inspecting the operators which are available for overloading reveals that there really are no good choices available for overloading which are both intuitive to use (ie resembles standard mathematical notation) and preserve proper operator precedence. This, in turn, leads us to the conclusion that the dot and cross operations should not be overloaded operators at all, but rather named functions.

It is good practice, IMO, to default to named functions, and to only overload operators when you are sure of its clarity and its accuracy in the face of operator precedence.
throw table_exception("(? ???)? ? ???");
johnstanp
johnstanp
Quote:
Original post by Ravyne
Quote:
Original post by hymerman
Apparently, none: http://www.gamedev.net/community/forums/topic.asp?topic_id=506184


If that's what you took away from that thread, then you're really misinterpreted what others have said.

A 'class' is two things -- data and interface. The interface is made up of both member and non-member functions, and in fact, non-member functions are usually preferable. Just because a function is defined outside of the class construct doesn't make it any less a part of the class (With the caveat that it must be defined in the same namespace as the class in order for Koenig look-up to be able to find it). I generally preserve member functions for functions which either are not implementable with the public interface (They have to be) or those which modify the class's state (which may have to be in the future, and so, should be now).

Quote:
My advice would be to code things as and when you need them, since you never know what will be needed and what will just be a waste of time.


For example, I've seen a couple C++ vector classes which overload some combination of *, % and ^ to indicate the various products associated with vectors. The problem in using the % and ^ operators is that they have even lower precedence than the + and - operators and can lead to code like this:

a % b + c ^ d // a b c d

which, one would naively assume, would be evaluated as (a%b)+(c^d), but instead is evaluated as a%(b+c)^d. All this is a result of poorly choosing operators to overload.


Yes, good advice!!!
It can be a painful process to realize that a very long formula( like torque computing in 3D, in order to have a rigid body in a desire state ) doesn't produce the expected result because of the precedence of some operators on the others...
Defining a "const Vector3 cross( const Vector3& )const" instead of "const Vector3 operator^( const Vector3& )const", adds moreover clarity to the code, and doesn't require familiarity with the interface.

hymerman
hymerman
Quote:
Original post by Ravyne
If that's what you took away from that thread, then you're really misinterpreted what others have said.


Not at all, I found it very useful actually, that was a vast simplification of just some of what was said. Perhaps I should have taken the time to write something more detailed!


Quote:
A 'class' is two things -- data and interface. The interface is made up of both member and non-member functions, and in fact, non-member functions are usually preferable. Just because a function is defined outside of the class construct doesn't make it any less a part of the class


Though not everyone agrees on that, apparently! But that's another discussion.

Quote:
Quote:
My advice would be to code things as and when you need them, since you never know what will be needed and what will just be a waste of time.


This is a re-statement of the YAGNI, or You Ain't Gonna Need It, principle. Unfortunately, IMO, some YAGNI proponents take it too far, or oversimplify when extolling its virtues to newbies.


Again something I should have expanded on. I didn't literally mean "only write the '+' operator the moment before you need to add two vectors", more that you should wait until some operations are needed before writing them rather than asking for a list. If you have to ask, you probably shouldn't be writing them :)

I could just see someone coming along and listing the Kronecker outer product, Gram-Schmidt orthonormal basis and a million other near-useless (apologies to mathematics bods) functions and him going off finding out what they mean so he can implement them, when he could be writing a game.
Ravyne
Ravyne
Quote:
Original post by johnstanp
Quote:
Original post by Ravyne
Quote:
Original post by hymerman
Apparently, none: http://www.gamedev.net/community/forums/topic.asp?topic_id=506184


If that's what you took away from that thread, then you're really misinterpreted what others have said.

A 'class' is two things -- data and interface. The interface is made up of both member and non-member functions, and in fact, non-member functions are usually preferable. Just because a function is defined outside of the class construct doesn't make it any less a part of the class (With the caveat that it must be defined in the same namespace as the class in order for Koenig look-up to be able to find it). I generally preserve member functions for functions which either are not implementable with the public interface (They have to be) or those which modify the class's state (which may have to be in the future, and so, should be now).

Quote:
My advice would be to code things as and when you need them, since you never know what will be needed and what will just be a waste of time.


For example, I've seen a couple C++ vector classes which overload some combination of *, % and ^ to indicate the various products associated with vectors. The problem in using the % and ^ operators is that they have even lower precedence than the + and - operators and can lead to code like this:

a % b + c ^ d // a b c d

which, one would naively assume, would be evaluated as (a%b)+(c^d), but instead is evaluated as a%(b+c)^d. All this is a result of poorly choosing operators to overload.


Yes, good advice!!!
It can be a painful process to realize that a very long formula( like torque computing in 3D, in order to have a rigid body in a desire state ) doesn't produce the expected result because of the precedence of some operators on the others...
Defining a "const Vector3 cross( const Vector3& )const" instead of "const Vector3 operator^( const Vector3& )const", adds moreover clarity to the code, and doesn't require familiarity with the interface.


You're welcome.

Careful though -- There's no reason for the cross (or other products) function to be a member since it should never change the state of either argument it takes. The same applies to the standard non-assigning mathematical operators (+, -, *, /, which should all be implemented in terms of their assigning counterparts, which should be members) as well. Remember, if it doesn't *have* to be a member function, then it *shouldn't* be a member function -- always default to non-member first. Note also, that the return value should not be const. It would look like:

Vector3 cross(const Vector3& lhs, const Vector3& rhs)


Back to the standard non-assigning operators, the canonical form looks like (using addition as an example):
Vector3& Vector3::operator+=(const Vector3& rhs){  X += rhs.X;  Y += rhs.Y;  Z += rhs.Z;  return *this;}Vector3 operator+(const Vector3& lhs, const Vector3& rhs){  Vector3 temp(lhs);  return temp += rhs;}

A variation that may allow certain compilers more leeway for optimization (pay close attention to the arguement types) is:
Vector3& operator+=(Vector3& lhs, const Vector3& rhs){  lhs.X += rhs.X;  lhs.Y += rhs.Y;  lhs.Z += rhs.Z;  return *this;}Vector3 operator+(Vector3 lhs, const Vector3& rhs){  return lhs += rhs;}

Note that, in this version the += operator is defined as a non-member function as given in the book "C++ Coding Standards" by Sutter and Alexandrescu. I'm not actually certain that this is required in order to reap the benefits of the potential optimization, since the member form, AFAIK, receives a pointer to a non-const Vector3, this, which ought to be equivalent to the non-const reference as far as optimization is concerned.

If anyone happens by and knows whether this non-member form *does* make the optimization possible, and *why* I would be glad to know. If it makes no difference, it seems to me that the member form is preferable, since it modifies the state of the object.
throw table_exception("(? ???)? ? ???");
SiCrane
SiCrane
Quote:
Original post by Ravyne
Note that, in this version the += operator is defined as a non-member function as given in the book "C++ Coding Standards" by Sutter and Alexandrescu. I'm not actually certain that this is required in order to reap the benefits of the potential optimization, since the member form, AFAIK, receives a pointer to a non-const Vector3, this, which ought to be equivalent to the non-const reference as far as optimization is concerned.

If you're referring to Item 27 in "C++ Coding Standards" re-read that section. The reason for using a non-member operator@= is because of Item 44, not optimization, as noted on the bottom of page 48. The optimization comment is referring to operator@ taking the first argument by value as mentioned at the top of page 49.
johnstanp
johnstanp
Quote:
Original post by Ravyne
You're welcome.

Careful though -- There's no reason for the cross (or other products) function to be a member since it should never change the state of either argument it takes. The same applies to the standard non-assigning mathematical operators (+, -, *, /, which should all be implemented in terms of their assigning counterparts, which should be members) as well. Remember, if it doesn't *have* to be a member function, then it *shouldn't* be a member function -- always default to non-member first. Note also, that the return value should not be const. It would look like:

Vector3 cross(const Vector3& lhs, const Vector3& rhs)


I think it's simply a matter of taste: the only( one of the ) benefit(s) of using a non-member function is allowing the compiler to make type conversion( via constructors ). I prefer defining all operators as member functions to stay close to the Object Oriented Paradigm...I simply believe that defining everything( operators included ) in the class, allows me to prevent temporary copies( type conversions ) by enforcing all my constructors to be explicit...
I only define non member functions for operators that cannot be implemented as member functions...I believe defining everything in the class is a good habit and eases the reimplementation of the code in another language, stricter( good grammar? ) on the OOP point of view.
And for operator overloading( +, -, *, .. ), I only use it for the clarity of the code and debugging ease: it's not essential after all...

For the constant return value: it is simply a way to avoid writing less readable code. I don't see the necessity of being able to write the following code:

v3 = v1.cross( v2 ).normalize();

I prefer:

v3 = v1.cross( v2 );
v3.normalize();

Its simply a matter of taste.

[Edited by - johnstanp on August 27, 2008 2:33:20 PM]
mzeo77
mzeo77
I suggest keeping the matrix and vector classes as mathematically strict as possible, and implement other functionality as functions (like cross product or normalize or generating rotation matrices). It makes the code clear and concise and closed for interpretations. In my opinion, the only methods in the class should be: matrix multiplication, transpose and inverse, element wise addition/substraction/scaling, and finally negation.

In fact a vector is rely just at special case of a matrix. of dimensions 1xN or Nx1. There are also good reasons for keeping a column vector different from a row vector. For instance a dot product is just implemented as an ordinary matrix multiplication. (vc1 * vc2.transpose()). Another advantage is that just by looking at the type of vector you know if is intended to be multiplied with a matrix from the left side or from the right side.

Example code:
typedef RVector3 Matrix<1, 3>;typedef CVector3 Matrix<3, 1>;RVector3 dot(const RVector3 & lhs, const RVector3 & rhs) {   return lhs * rhs.transpos();}Matrix<4, 4> translate(float x, float y, float z) {   Matrix<4, 4> result = Matrix<4, 4>::Identity();   result(3, 0) = x;   result(3, 1) = y;   result(3, 2) = z;   return result;}Matrix<4, 4> m = rotateX(90) * rotateY(90) * translate(1, 2, 3);




johnstanp
johnstanp
I don't agree with you...I think that calling everytime a function that generates a temporary object( v2.transpose() ), is not recommanded for maximum performance. Since it is an operation that will be done a great number of times, it would be better to keep one Vector type and defining all operations accordingly...And the two types only diverge on one single characteristic: their conceptual disposition...You will pay a performance price while you can do exactly the same operation without paying the price of constructing and destructing a temporary object each time you call the function.
That kind of distinction( colum- and row-vectors ) is necessary for a scientific software like Matlab( it makes sense for a lot of algorithms ), but I don't see its purpose in game programming.

[Edited by - johnstanp on August 27, 2008 3:11:49 PM]
Ravyne
Ravyne
Quote:
Original post by SiCrane
Quote:
Original post by Ravyne
Note that, in this version the += operator is defined as a non-member function as given in the book "C++ Coding Standards" by Sutter and Alexandrescu. I'm not actually certain that this is required in order to reap the benefits of the potential optimization, since the member form, AFAIK, receives a pointer to a non-const Vector3, this, which ought to be equivalent to the non-const reference as far as optimization is concerned.

If you're referring to Item 27 in "C++ Coding Standards" re-read that section. The reason for using a non-member operator@= is because of Item 44, not optimization, as noted on the bottom of page 48. The optimization comment is referring to operator@ taking the first argument by value as mentioned at the top of page 49.


Yes, that's exactly what I was referring too. Thanks for pointing that out, I'm not sure how I read around that paragraph. I was certain that the non-member assigning function wasn't a part of the optimization, though, if they wished to make a point about it being good practice I wonder why they didn't simply use it in the first example as well.

Quote:
Original post by johnstanp
Quote:
Original post by Ravyne
You're welcome.

Careful though -- There's no reason for the cross (or other products) function to be a member since it should never change the state of either argument it takes. The same applies to the standard non-assigning mathematical operators (+, -, *, /, which should all be implemented in terms of their assigning counterparts, which should be members) as well. Remember, if it doesn't *have* to be a member function, then it *shouldn't* be a member function -- always default to non-member first. Note also, that the return value should not be const. It would look like:

Vector3 cross(const Vector3& lhs, const Vector3& rhs)


I think it's simply a matter of taste: the only( one of the ) benefit(s) of using a non-member function is allowing the compiler to make type conversion( via constructors ). I prefer defining all operators as member functions to stay close to the Object Oriented Paradigm...I simply believe that defining everything( operators included ) in the class, allows me to prevent temporary copies( type conversions ) by enforcing all my constructors to be explicit...
I only define non member functions for operators that cannot be implemented as member functions...I believe defining everything in the class is a good habit and eases the reimplementation of the code in another language, stricter( good grammar? ) on the OOP point of view.
And for operator overloading( +, -, *, .. ), I only use it for the clarity of the code and debugging ease: it's not essential after all...


While it's not the worst thing you can do, its not really a matter of taste or opinion, and its certainly not "stricter on the OOP point of view" because it is, in fact, quite the opposite. Non-member, non-friend functions are not allowed to access the private members of the class to which they pertain; they must be implemented using the same public interface everyone is presented with. This *improves encapsulation* and *reduces coupling*, and its practice serves to make classes less monolithic -- All good things from the "OOP point of view". If the function (rightly) requires access to the class's internals, then make it a member function. According to the experts, you've got things backwards -- You should only make the functions a member when you have not, not only make functions a non-member when you have to.

Quote:
For the constant return value: it is simply a way to avoid writing less readable code. I don't see the necessity of being able to write the following code:

v3 = v1.cross( v2 ).normalize();

I prefer:

v3 = v1.cross( v2 );
v3.normalize();

Its simply a matter of taste.


I used to subscribe to the same line of thinking, however, I've since stopped trying to babysit the programmers making use of my code (most often, myself). Just because you may not like the first line of code, others (myself included, in this instance) might see nothing wrong with it, or even prefer it. As someone who provides code to others to use (even if only just yourself) it is your job to help them get things done, not to tell them how to to their job "The Right Way" (TM). When wearing your "API provider" hat, you must have faith that your clients won't intentionally sabotage themselves. The crime you're committing by enforcing the const return value is in reducing the expressiveness of the code you've provided.

I ask, what would happen if you changed to a non-const return value? You're preferred code would still work, correct? There is no additional overhead, correct? So, what is it that you gain by adding const? Nothing. It doesn't affect, at all, your preferred way of doing things. What do your clients (even, perhaps, your future self) lose by being forced to bear the burden of a const return? They loose the ability to express their code in the way they see fit. You're taking something away from them, and gaining nothing for yourself. You're forcing your opinions upon them. Its simply The Wrong Thing (TM) to do -- and while this is my own opinion, I believe the more learned members of this forum would agree.

Beyond all of this "API morality" speak, it violates the cardinal fall-back of class design -- When in doubt, do as the ints do. The ints and their mathematical operators all return non-const, which allows some weird and seemingly useless lines of code, however, this is a known and accepted part of the language. To go against this is to go against the Principle of Least Surprise and can only lead to headaches for your clients.
throw table_exception("(? ???)? ? ???");
johnstanp
johnstanp
Quote:
Original post by Ravyne
While it's not the worst thing you can do, its not really a matter of taste or opinion, and its certainly not "stricter on the OOP point of view" because it is, in fact, quite the opposite. Non-member, non-friend functions are not allowed to access the private members of the class to which they pertain; they must be implemented using the same public interface everyone is presented with. This *improves encapsulation* and *reduces coupling*, and its practice serves to make classes less monolithic -- All good things from the "OOP point of view".


In object oriented, everything must be defined inside a class...C++ is not totally object oriented because it offers the possibility of procedural programming( non-member functions )...You cannot for instance declare a function in Java outside of a class. If you try reimplementing your C++ code that makes heavy use of non member function in an Object Oriented Language, that doesn't allow procedural programming, you'll understand why C++ is not a pure Object Oriented Programming Language.
The only non-member functions I define are friend functions( for speed issues in game programming ): they are in fact an extension of the interface of my classes, since they depend of its implementation.
Yes, friend function violates encapsulation, but I only use them as an extension of my classes.

Quote:

If the function (rightly) requires access to the class's internals, then make it a member function. According to the experts, you've got things backwards -- You should only make the functions a member when you have not, not only make functions a non-member when you have to.


When I have the choice, I choose the one that respect OOP. I don't have to define non-member functions when I can define them inside the class, and put all the relevant code in the scope of the class...It's simply a matter of choice, since C++ offers you both possibilities: procedural programming and/or object oriented programming.
If I can write

template< typename Real, unsigned int N >class Vector{	public:		Vector() : elem_() {}		Real operator[]( unsigned int i )const		{ 			if( i < N )			{				return elem_;			}			else			{				//throw an exception;			}		}		Real length()const		{			Real sum = 0.0;						for( int i = 0; i < N; ++i )			{				sum += elem_ * elem_;			}			return sqrt( sum );						}		}	private:			Real elem_[N];};


then why would I want to write:

#include "Vector.hpp"template< typename Real, unsigned int N >Real length( const Vector< Real, N >& vector ){	Real sum = 0.0;	for( int i = 0; i < N; ++i )	{		sum += vector * vector;	}	return sqrt( sum );}


I've used a template function, meaning that the size of the vector is known at compile time and that offers optimization possibilities( that can't be always the case if the length of the vector is not known at compile time )...Even with the size known at compile time, you still call a function( Real operator[]( unsigned int i )const ), that checks for every access of the elem_ array the index validity...while the member function doesn't even need such a checking for accessing its own data. If I hadn't used templates, the non-member function would need to know the size of the vector, and would need a function call( template< typename Real, unsigned int N > unsigned int Vector< Real, N > getSize()const; ) for the loop.
This is a simple example, showing that you'd better define your functions inside your classes, because each object has direct access to its data, and information related to them...
[edit]You didn't say the opposite( or exact oppposite ), when reading again the quote. We were talking of a vector class and someone suggested( in a previous thread ) that the length function should be a non-member function...I see no valuable reason for this. The "core classes" are likely to have everything( or almost ) defined inside them...When I talk about defining everything in a class, I refer to the basic functionalities: you can implement a complex operation on an object outside its class, if it is not a requirement in its interface...But even, I generally prefer implementing complex functions inside a class, or as a functor object adding a state...Example: I need updating my RigidBody object with an integration method...I give that responsibility to another class( yes, still no non-member function ), adding a state( I can change its internal state. )

Quote:

Quote:
For the constant return value: it is simply a way to avoid writing less readable code. I don't see the necessity of being able to write the following code:

v3 = v1.cross( v2 ).normalize();

I prefer:

v3 = v1.cross( v2 );
v3.normalize();

Its simply a matter of taste.


I used to subscribe to the same line of thinking, however, I've since stopped trying to babysit the programmers making use of my code (most often, myself). Just because you may not like the first line of code, others (myself included, in this instance) might see nothing wrong with it, or even prefer it. As someone who provides code to others to use (even if only just yourself) it is your job to help them get things done, not to tell them how to to their job "The Right Way" (TM). When wearing your "API provider" hat, you must have faith that your clients won't intentionally sabotage themselves. The crime you're committing by enforcing the const return value is in reducing the expressiveness of the code you've provided.


This is not babysitting, because I code first for myself and there's no speed gain, readability in writing:

v3 = v3.cross( v2 ).normalize();

If the cross function returns a non const Object, I can even write:

v3.cross( v2 ) = v1;

which is perfectly useless.

Quote:

I ask, what would happen if you changed to a non-const return value? You're preferred code would still work, correct? There is no additional overhead, correct? So, what is it that you gain by adding const? Nothing. It doesn't affect, at all, your preferred way of doing things. What do your clients (even, perhaps, your future self) lose by being forced to bear the burden of a const return? They loose the ability to express their code in the way they see fit. You're taking something away from them, and gaining nothing for yourself. You're forcing your opinions upon them. Its simply The Wrong Thing (TM) to do -- and while this is my own opinion, I believe the more learned members of this forum would agree.


You gain something: clarity, readability and more you restrict the operations that can be done on the result of the function. Why would I leave me the possibility of creating a temporary variable that would have no use, since I can assign it another object?

v3.cross( v2 ) = v1;

It's a choice and I respect yours: if you like coding that way, it's your choice. Your compiler won't complain, just like it won't complain about my coding choices.

Quote:

Beyond all of this "API morality" speak, it violates the cardinal fall-back of class design -- When in doubt, do as the ints do. The ints and their mathematical operators all return non-const, which allows some weird and seemingly useless lines of code, however, this is a known and accepted part of the language. To go against this is to go against the Principle of Least Surprise and can only lead to headaches for your clients.


The client only does what he is allowed to do...As long as you give him the possibility to achieve his goals, he will have to follow your rules, and if that means clearer and easier to maintain code, he will benefit from them...

[Edited by - johnstanp on August 28, 2008 4:22:57 AM]
johnstanp
johnstanp
Quote:
Original post by MrPickle
What functions would a good 2D & 3D vector class have?
I plan on making 2 seperate classes for each.

Dot, Normalize etc?


Only put inside your class, the strict minimum...You don't have to define a distance function for example, if you have a length( squaredLength ) function and overloaded operator "-" inside your class...Unless you need computing it many times, in many parts of your application.
ToohrVyk
ToohrVyk
Quote:
Original post by Ravyne
I ask, what would happen if you changed to a non-const return value? You're preferred code would still work, correct? There is no additional overhead, correct? So, what is it that you gain by adding const? Nothing. It doesn't affect, at all, your preferred way of doing things. What do your clients (even, perhaps, your future self) lose by being forced to bear the burden of a const return? They loose the ability to express their code in the way they see fit. You're taking something away from them, and gaining nothing for yourself. You're forcing your opinions upon them. Its simply The Wrong Thing (TM) to do -- and while this is my own opinion, I believe the more learned members of this forum would agree.
Careful, there... removing freedom from your clients can actually be a blessing (for them) if it prevents them from doing stupid things, and many experienced programmers go to great lengths to restrict their own freedom. That's the entire reason things like a type system or private variables exist. The actual question is not whether it is moral to prevent your clients from doing things as they like, but rather to know if the provided freedom allows a frequent error to happen (so that it would deserve to be removed).

The answer here is that, indeed, people don't mistakenly apply a mutator to a temporary value, because the only situation where that odd construct (foo().bar()) happens is when they're using a chaining idiom (the mutator returns a reference to the temporary, which may be stored or chained again). The only exception here is the classic if(foo() = constant) mistake, which does not happen for vectors because no sane person would equality-compare vectors anyway (that's usually done using a near() function).

And therefore, adding a const prevents a sane idiom (chaining) while not eliminating any actual danger, which is why it should not be done.

Quote:
Original post by johnstanp
In object oriented, everything must be defined inside a class...C++ is not totally object oriented because it offers the possibility of procedural programming you've just defined( non-member functions )...You cannot for instance declare a function in Java outside of a class. If you try reimplementing your C++ code that makes heavy use of non member function in an Object Oriented Language, that doesn't allow procedural programming, you'll understand why C++ is not a pure Object Oriented Programming Language.
The only non-member functions I define are friend functions( for speed issues in game programming ): they are in fact an extension of the interface of my classes, since they depend of its implementation.
Ah, the classic (pun intended) mix between "put everything in classes" and "object oriented programming". I'm quite sorry to disappoint you, but neither implies the other.

For the put-everything-in-classes side, it's perfectly possible to use classes as plain old namespaces crammed with static member functions, and write a state-of-the-art procedural program in Java or just about any other language usually considered to be pure Object Oriented Programming Language. Doing so is extremely easy, so easy that many people do so without even realizing (with the Singleton Pattern being the spearhead of doing procedural programming in object-oriented languages by so readily and easily translating the good old procedural concept of module into a Java-friendly form without changing any of its non-object-oriented nature).

Remember that writing object-oriented code is much more than putting things into objects or classes. It involves using a vast array of techniques to eliminate coupling, while letting things as they are if the cost of refactoring everything in a pure object-oriented way is too complex. Any piece of software must accept that some parts of it will not be abstracted away using object-oriented techniques. In the end, people who think that non-member functions are "procedural" tend to be confident that if they don't use non-member functions, they're not writing procedural code and therefore their code must be object-oriented. This is the path to the dark side. It is advisable that you rethink your definitions of procedural and object-oriented, because as they stand they certainly don't match the definitions that most other people have.

On the other side, well... first, there's the point of "put everything into classes" which is kind of weird when considering object-oriented languages which support immediate (class-less) objects, such as OCaml:
window # add_button  object     method onClick = print_endline "Button was clicked"  end
Or, of course, object-oriented languages which support runtime decoration of objects with additional methods. But this is a minor nitpick, as I suspect you meant to "put everything into objects" (because adding things to classes as opposed to objects implies static behavior, which is procedural by its very nature).

Then, there's the mismatch between design and implementation. See, there is no design difference between having a static function (possibly outside a class) which takes a reference to an object, and a non-overridable member function of that object: although the syntax is slightly different, the two are used in exactly the same way (this is, by the way, the basic principle for writing object-oriented software in C). If one of them is a valid object-oriented design, then so is the other, because there is no design difference between the two.

As a side note, a good Object-Oriented Design reduces the number of friend functions (including those member functions that access non-public state) as much as possible. Since it's easier to restrict the access of a function to non-public state by moving it out of the function, good object-oriented C++ programmers move functions out of the class if they don't need to be there, in order to improve the encapsulation of the class and thus achieve a better object-oriented design.
Dtag
Dtag
The downside of using non-member functions is obviously that you get a non-uniform interface for the class. I.e you may have to write
yourclass.someOperation() for one thing and someOtherOperation(yourclass). Iam quite sure that people wouldnt be so much 'offended' by the latter notation if it wasnt so different to the first one.

Couldnt it be that C++ (and also Java etc), miss a language construct to unify these notations? I.e have something next to public/private/protected that allows declaring member functions that dont have access to the protected/private areas of the class. That would still allow you to write yourclass.someOperation(), and in case of changes to internals of the class, you would still be sure that someOperation wouldnt be affected.
SiCrane
SiCrane
Quote:
Original post by Dtag
The downside of using non-member functions is obviously that you get a non-uniform interface for the class. I.e you may have to write
yourclass.someOperation() for one thing and someOtherOperation(yourclass). Iam quite sure that people wouldnt be so much 'offended' by the latter notation if it wasnt so different to the first one.


If you're offended by needing to write object.foo() instead of foo(object), then it's trivial to create a function foo(object) that calls object.foo().

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.