Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static methods vs local methods within a class

Tags:

c++

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?

like image 957
Master Chief Avatar asked Aug 24 '26 04:08

Master Chief


2 Answers

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.

like image 156
K-ballo Avatar answered Aug 26 '26 18:08

K-ballo


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();
}
like image 38
Dietmar Kühl Avatar answered Aug 26 '26 16:08

Dietmar Kühl



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!