Master Chief
Master Chief

Reputation: 75

Static methods vs local methods within a class

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?

Upvotes: 3

Views: 229

Answers (3)

K-ballo
K-ballo

Reputation: 81399

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.

Upvotes: 3

coelhudo
coelhudo

Reputation: 5080

Besides the other answers about differences between the two approaches, the static methods are difficult to mock/stub if you want to employ in unit tests.

For example, suppose that you have a class named ClassThatUsesQuaternion that uses Quaternion. If Quaternion have a lot of static methods you will always have real data. On the other hand, if you transform Quaternion methods into virtual methods, you will be able to redefining all methods, creating a environment test under your control. You can even add a mock framework like gmock to put your expectations.

Upvotes: 0

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 154035

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();
}

Upvotes: 1

Related Questions