Requesting peer review of 3D Vector class.

Started by
53 comments, last by jpetrie 15 years, 9 months ago
Quote:Original post by dmatter
Quote:Yeah. Bizarre, isn't it? But file streams actually use it to let you test if a stream is good or not!
Yeah but a zeroed vector isn't a 'bad' vector. [smile]


Neither is a zeroed integer. Although, I've decided I'll fix this when AND IF the problem occurs.

Also: even though I do a vector = vector * float every frame, I've never had a problem with it not know whether it should convert the vector to a bool. Partially because the problem would only come up if the vector was NOT a float. Even so, I found that by making the second operand a different class according to the template, I didn't have to worry about this.
Quote:Original post by bitshifter
Debugging code is twice as hard as writing it.
So if you write the code as cleverly as possible,
it is virtually impossible for you to debug it.


Mind pointing out some particularly clever code? Also: if it's clever, but easy to understand, it's no harder to debug. The problem, and this is where that quote came from, is when code is completely unreadable.

Quote:Original post by dmatter
The rule is to use non-members whenever members aren't necessary. The exceptions to the rule are to use members if that makes things significantly more efficient (normalise is arguably an example of this) or when they improve the useability of the class (can think of no examples for a vector).


The suggestion is. The only RULES I have to worry about should be those set upon me by my compiler and C++. My personal preference dictates that variables should be as PUBLIC as possible (I won't use accessors) and functions should be in a class, not for. I will consider your suggestions and reject your rules.

That being said, someone suggested I show the updated version, so I replaced the one up top with the new one (v2).
Advertisement
T xyMagnitude() const { return sqrt( yzMagnitudeSqr() ); }T yzMagnitude() const { return sqrt( xyMagnitudeSqr() ); }

These should be:
T xyMagnitude() const { return sqrt( xyMagnitudeSqr() ); }T yzMagnitude() const { return sqrt( yzMagnitudeSqr() ); }
Quote:Original post by Splinter of Chaos
My personal preference dictates that variables should be as PUBLIC as possible (I won't use accessors) and functions should be in a class, not for. I will consider your suggestions and reject your rules.


I think that accessors (getx(), gety(), etc...) are a bit silly, verbose, and over-engineered for a vector class.

I use class member functions to either operate on the class's data and global functions to operate using the class's data. Thus vector.normalize() and normalize(vector) are both perfectly valid from my point of view, but both have entirely different meanings: the global function returns a normalized version of the input vector whereas the member function normalizes itself.

Quote:Original post by Splinter of Chaos
Quote:Original post by dmatter
The rule is to use non-members whenever members aren't necessary. The exceptions to the rule are to use members if that makes things significantly more efficient (normalise is arguably an example of this) or when they improve the useability of the class (can think of no examples for a vector).


The suggestion is. The only RULES I have to worry about should be those set upon me by my compiler and C++.
Different compilers have different rules. As for C++, it will let you get away with a lot of bad and evil things but what I was referring to as "rules" is really called idiomatic C++.

Quote:My personal preference dictates that variables should be as PUBLIC as possible (I won't use accessors) and functions should be in a class, not for. I will consider your suggestions and reject your rules.
Fair enough - but just to point out that this runs counter to idiomatic C++: Variables should be as private as possible and functions should only be made members of a class whenever necessary - See Scott Meyers' article.

Quote:Original post by CrimsonSun
I think that accessors (getx(), gety(), etc...) are a bit silly, verbose, and over-engineered for a vector class.

You can get away without accessors, certainly, but I'd argue that since it is so trivial to suggest alternate implementations...

1) float i, j, k;
2) float v[3];

... that accessors/mutators really make complete sense here.
That said, I don't like your accessors/mutators, I prefer something like these:

// an accessor
const T & x() { return i; /*return v[0];*/ }

// a mutator
void y(const T & value) { j = value; /*v[1] = value;*/ }

// or, an accessor/mutator combined
T & z() { return k; /*return v[2];*/ }

So then you have:

// access
float value = vec.x();

// mutate
vec.y(value);

// or, access then mutate
vec.z() = value;

Not only is that not a really much more verbose that having plain public variables but it's encapsulated the implementation.
Quote:Original post by Splinter of Chaos
The suggestion is. The only RULES I have to worry about should be those set upon me by my compiler and C++.


There are other rules which apply to you. Namely those biological rules about how much information your brain can handle, or how information is best structured to be manipulated by the human brain. The gist of these rules is, the simpler, the better.

Quote:My personal preference dictates that (...) functions should be in a class, not for. I will consider your suggestions and reject your rules.


Sure, that's not a rule. But let me ask you a question, first. Why don't you simply use this simple definition?
struct vector3d { float x, y, z; };


You don't need accessors, operators, functions or whatever. The only thing that the rules of C++ and of your compiler require is the definition of three member variables, to store the three coordinates of your vector. Why, then, did you make the effort of defining all these entities? After all, if you need to add two vectors together, you can always use the built-in float operators on the individual coordinates.

It's quite probable that your answer to the above question will be along the lines of "It's easier to write the code once and reuse it everywhere", and indeed this decision stems from the elementary observation that code reuse is good for productivity.

Using free functions instead of member functions follows the same principle. There are several good reasons why a free function is easier to reuse than a member function (functional programming within the standard library being one of them). Therefore, if your objective is to write short, clever, readable and elegant code, using free functions is the superior alternative.

Of course, it is your own personal decision to use member functions, and we have no authority (or reason) to force you to write short, clever, readable and elegant code, even if that was the original reason why you started this thread.
Quote:Original post by dmatter
Quote:Original post by Splinter of Chaos
Quote:Original post by dmatter
The rule is to use non-members whenever members aren't necessary. The exceptions to the rule are to use members if that makes things significantly more efficient (normalise is arguably an example of this) or when they improve the useability of the class (can think of no examples for a vector).


The suggestion is. The only RULES I have to worry about should be those set upon me by my compiler and C++.
Different compilers have different rules. As for C++, it will let you get away with a lot of bad and evil things but what I was referring to as "rules" is really called idiomatic C++.

Quote:My personal preference dictates that variables should be as PUBLIC as possible (I won't use accessors) and functions should be in a class, not for. I will consider your suggestions and reject your rules.
Fair enough - but just to point out that this runs counter to idiomatic C++: Variables should be as private as possible and functions should only be made members of a class whenever necessary - See Scott Meyers' article.


OK, fair 'nuff to say my program isn't idiomatic C++, in which case rule is the appropriate word.

Funny you should mention Scott Meyers: I'm actually reading Effective C++ (Third Edition) right now! Not that I agree with everything he says, though. But, he does mention in that article that things that will break the class, if altered, should be privates, which I agree with, but he uses a point class to demonstrate. Changing the x or y wouldn't kill the rest of the class and it's practically no different from using the accessors! (Unless for some ironically unorthodox reason, work needs to be done on the variables before they're set.)

I also like the part explaining how some classes have inconsistencies because they are either member or non-member and without a discernible pattern. "If you reflect a bit and are honest with yourself, you'll admit that you have this alleged inconsistency with all the nontrivial classes you use, because no class has every function desired by every client." I guess that means that if someone wants to extend the class, they should either make an extended class, edit the original source code, or it really should be programmed how he suggests...I just won't accept it, though.
Quote:Original post by ToohrVyk
Sure, that's not a rule. But let me ask you a question, first. Why don't you simply use this simple definition?
struct vector3d { float x, y, z; };


Because it's not simpler or easier to USE.
vect = normalize( vect ); 
is so much more typing than
vect.normalize();
and doesn't get the point across any better, and doesn't leave me the option of writing a non-member normalize that doesn't alter the data.
Quote:
Because it's not simpler or easier to USE.

vect = normalize( vect );

is so much more typing than

vect.normalize();

and doesn't get the point across any better, and doesn't leave me the option of writing a non-member normalize that doesn't alter the data.

Be careful here. As a programmer, keystrokes are trivial, you should not use keystroke count to gate the simplicity or usability of code -- it's roughly akin to judging a book by it's cover; pure superficiality. If four or five characters is really that much of a deciding factor, you might want to consider a new hobby, because you're going to be typing a lot more than that.

Either that or you can take that concern to its logical conclusion and use a incrementing alphabetical system for all your identifiers -- (a, b, c... aa, bb, cc) et cetera. Succinctness of keystrokes (not meaning, which is different) should really only ever be a minor, last-bullet-point kind of concern.

I disagree stylistically with you, too. I don't think it's more clear what vect.normalize() is doing -- it could be modifying the invoking object, or it could be returning into the ether (e.g., a bug). The fact that the non-member version has a return makes things more clear, in my mind. But that's mostly style and not an objective argument; it can, after all, suffer the same bug.

However, when you say that the non-member option doesn't give you the option of a non-member, non-modifying version -- that doesn't make sense, since that's exactly what the non-member option is. Did you mean to say something else?
Quote:Original post by Splinter of Chaos
Because it's not simpler or easier to USE.
vect = normalize( vect ); 
is so much more typing than
vect.normalize();
and doesn't get the point across any better, and doesn't leave me the option of writing a non-member normalize that doesn't alter the data.


How kind of you to give that precise example I was expecting.
std::transform(vectors.begin(), vectors.end(), vectors.begin(), &normalize);


Your turn, using member functions, without giving up on the inlining (like std::mem_fun_ref risks to cause) [smile]
In C i would use...
typedef struct{   union   {      GLfloat _3fv[3];      struct      {         GLfloat x;         GLfloat y;         GLfloat z;      };   };} GLtype3fv;


In Cpp i would use...
class CType3fv{public:   union   {      GLfloat m_3fv[3];      struct      {         GLfloat m_x;         GLfloat m_y;         GLfloat m_z;      };   };};

Now the data type can be used in more ways than just a simple vector.
And also can be used as args to functions that expect ptrs EX: glVertex3fv()
Quote:Original post by bitshifter
In C i would use...
typedef struct{   union   {      GLfloat _3fv[3];      struct      {         GLfloat x;         GLfloat y;         GLfloat z;      };   };} GLtype3fv;


In Cpp i would use...
class CType3fv{public:   union   {      GLfloat m_3fv[3];      struct      {         GLfloat m_x;         GLfloat m_y;         GLfloat m_z;      };   };};

Now the data type can be used in more ways than just a simple vector.
And also can be used as args to functions that expect ptrs EX: glVertex3fv()


I wouldn't change the C code to something else since it already works fine for C++ as well.
The anonymous union and struct are not standard C or C++ so compilers could produces errors. Most of the compilers support it though.

In my vector math library i simply used a C++ conversion operator to T* which also allows me to index the vector.

This topic is closed to new replies.

Advertisement