First, to get my concern across take a look at these two code segments I have prepared:
struct Quaternion
{
public:
float X, Y, Z, W;
Quaternion(float x, float y, float z, float w)
:
X(x), Y(y), Z(z), W(w)
{}
void Normalise()
{
float num = (((this->X * this->X) + (this->Y * this->Y)) +
(this->Z * this->Z)) + (this->W * this->W);
float num2 = 1.0f / (static_cast<float>(std::sqrt(static_cast<double>(num))));
this->X *= num2;
this->Y *= num2;
this->Z *= num2;
this->W *= num2;
}
void Conjugate()
{
this->X = -this->X;
this->Y = -this->Y;
this->Z = -this->Z;
}
};
The above being the 'local methods' within the class that I am referring to in the title.. Now lets take a look at what I mean by the 'static methods' inside the class.
struct Quaternion
{
public:
float X, Y, Z, W;
Quaternion(float x, float y, float z, float w)
:
X(x), Y(y), Z(z), W(w)
{}
static Quaternion& Normalise(Quaternion& quat)
{
float num = (((quat.X * quat.X) + (quat.Y * quat.Y)) +
(quat.Z * quat.Z)) + (quat.W * quat.W);
float num2 = 1.0f / (static_cast<float>(std::sqrt(static_cast<double>(num))));
// Assuming operator= overloaded..
quat = Quaternion(quat.X * num2, quat.Y * num2, quat.Z * num2, quat.W * num2);
return quat;
}
static Quaternion& Conjugate(Quaternion& quat)
{
// Assuming operator= overloaded..
quat = Quaternion(-quat.X, -quat.Y, -quat.Z, quat.W);
return quat;
}
};
My question is.. What is the tradeoff? The effect? To using these static class methods rather than local methods. Both have similar usage:
Edit: Ignore the *.ToString functionality, it is psuedocode - I'm sure you can imagine what it would do; therefore its implementation is redundant as it just prints out raw X, Y, Z, W values.
The 'local method' class usage:
int main()
{
Quaternion testQuat(6.0f, 6.0f, 6.0f, 1.3f);
std::cout << testQuat.ToString(); // (6, 6, 6, 1.3)
testQuat.Conjugate();
std::cout << testQuat.ToString(); // (-6, -6, -6, 1.3)
return 0;
}
Now the 'static method' class usage:
int main()
{
Quaternion testQuat(6.0f, 6.0f, 6.0f, 1.3f);
std::cout << testQuat.ToString(); // (6, 6, 6, 1.3)
testQuat = Quaternion::Conjugate(testQuat);
std::cout << testQuat.ToString(); // (-6, -6, -6, 1.3)
return 0;
}
So what is the difference? These are static methods not objects. Which is preferable? Is it just a matter of design choice?
They are two totally different things. One of them modifies the object in place a la OOP, the other returns a different object a la functional style. If it was my choice, I would keep both of them as there are use cases for both of them. And I would implement the functional styles as free functions based on the member functions, i.e.:
Quaternion normalize( Quaternion quat )
{
quat.normalize();
return quat;
}
[I'm explicitly taking quat by value here, gives a chance for copy-elision]
Note that your static implementations are wrong, they are returning a reference to a temporary. That's undefined behavior, you should get a warning from your compiler and if you are lucky enough a runtime crash as well.
First off, your second approach shouldn't compile although I think MSVC++ has an error allowing temporary objects to be bound to non-const references. Even adding const& doesn't make the functions better: They still don't work because the caller gets hold of a reference to a temporary. So much for the implementation.
With respect to interface design, I think the real trade-off isn't between static members (you can have them additionally, if you want to) but whether the functions taking no parameters should mutate the object themselves or should return a correspondingly modified object:
// return a copy:
Quaternion Quaternion::conjugate() const {
return Quaternion(-this->X, -this->Y, -this->Z, this->W);
}
// modify the object itself:
void Quaternion::conjugate() {
this->X = -this->X;
this->Y = -this->Y;
this->Z = -this->Z;
}
Although these two overload actually can live in the same class I would not provide both of them! It is a choice of interface which one is preferable. I would personally prefer the latter and probably create a static member mutating the object itself:
/* static */ void Quaternion::conjugate(Quaternion& object) {
object = object.conjugate();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With