Jump to content
  • Advertisement
Sign in to follow this  
CodeCriminal

Motivation for global as opposed to member functions

This topic is 2905 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi all, this question has been lingering in the back of mind for some time now, and so im here to get your thoughts and opinions on global vs member functions.

Why 'should' I prefer global functions to member functions? what possible benefit does this have?
Take for example, the std::pair template class, its operator overloads (==, < ..) are global, how is this any different than overloading the operators inside the class definition? Also, why not use static members as opposed to global functions?

I have read Effective C++ where Mike talks about this topic, but it was a while ago and even then I wasnt too sure I agreed with his logic (but what do I know, I am no C++ expert with years of experience behind me).

So, if anyone could fill me in on this, that would be great, possibly with solid examples to show times I 'should' prefer global over member functions.

Thanks.

Share this post


Link to post
Share on other sites
Advertisement
*Free* functions. Some free functions may be global (static).

The opposite question would be: why make everything member functions?

Think of the following problem:
struct Rect {
int x,y;
};

struct Sprite : Rect {
void move_by(int x, int y) {
this->left += x;
this->top += y;
}
};

struct Window : Rect {
void set(int x, int y, int w, int h) { // the stuff }
};
The above looks clean, simple, nice inheritance.

But now we add another class - image, which should also have move_by() or set(). Suddenly we have two choices - move one of those functions into Rect and pollute the Window as well, or copy paste code.

Thinking from design perspective:
- Rectangle is 4-tuple (x,y,w,h)
- Many operations can be performed on it, such as move_by, set, scale, ...
- These operations apply to rectangle, regardless of what it represents, be it sprite, window, image

So the new API can be:
struct Sprite {
Rect r;
};

struct Window {
Rect r;
};

void move_by(Rect & r, int dx, int dy);
void set(Rect & r, int x, int y, int w, int h);
...


While second approach may seem less logical (and, all those could be member functions of Rect), it is better from development perspective:
- In order to extend functionality of Rect, one doesn't need to modify definition of Rect.

That may seem like a minor point, but in practice it is often undesirable to change existing class - and C++ offers no way of extending class definition without modifying it and recompiling everything.

Even more, various traits of Rect can be defined in more than one place, depending on needs.

It's a pretty big deal as projects grow.

Second part, even more important - it is easier to test. In order to test member functions of a class, I need to create a whole instance (perhaps very complicated or expensive), then call functions, and somehow (no clue how) verify that internals are working correctly.

With free functions, data is simple - to test Rect-related functions, I just create a bunch of Rects, then test their contents before and after - all mutable information is exposed publicly. STL containers are a good example of this design - they have internal state, but almost all of it can be inspected externally.

And back to previous issue, such tests are easily extended, either in single place or across multiple files.

Share this post


Link to post
Share on other sites
Quote:
*Free* functions. Some free functions may be global (static).


Ah yes, sorry my bad. :)


I understand what your saying in some respects but other areas seem still a little grey to me. The way I see it is, these free functions do not particularly 'extend' functionality of a class, merely wrappping existing functionality for convenience sake.

I noticed that your data members are public, this would violate encapsulation principles in most cases that are more sophisticated than a simple rect structure.

Lets say you have a class of which you are trying to provide free functions for, (to 'extend' functionality) this particular class has private data members, with no way of accessing them from the 'outside'.
Without violating the whole point (by extending the defintion of said class) and without violating encapsulation (i.e. making the members public prefer private data members to public), how would you suggest one might go about extending the functionality in this situation. As far as I know you can't, you can only wrap it existing functionality into more convenient packaging.

Quote:
Second part, even more important - it is easier to test. In order to test member functions of a class, I need to create a whole instance (perhaps very complicated or expensive), then call functions, and somehow (no clue how) verify that internals are working correctly.

With free functions, data is simple - to test Rect-related functions, I just create a bunch of Rects, then test their contents before and after - all mutable information is exposed publicly. STL containers are a good example of this design - they have internal state, but almost all of it can be inspected externally.


This is making no sense to me what-so-ever. In either case you still have to instantiate a rect structure (or any structure for that matter), you still have to call one or more functions, and verifying internals... isn't that something you would do during a debug step-through? not completely clear on that one.

// member
Rect r = {x, y, w, h};
r.move_by(x, y, w, h);

// non-member
Rect r = {x, y, w, h};
move_by(r, x, y, w, h);

// global (static)
Rect r = {x, y, w, h};
Rect::move_by(r, x, y, w, h);








Also, if the classes happend to be more complicated than simple constructor instantiation (i.e. they needed various methods to be called to ensure they are constructed properly) the same steps would have to be taken in order to test for either case.

Im not sure that this should be a hard and fast rule, as I felt it was presented in the book Effective C++ Third Edition (just took another look) I see no benefit for the majority of cases.
In my opinion, a free function should be prefered when the need arises to wrap existing functionality into a unified interface without cluttering up the class definition.

I know im going to get a slap round the face in a minute followed by an "Oohhh yeah.." so if you wouldnt mind continuing ? :P

Share this post


Link to post
Share on other sites
Quote:
Original post by CodeCriminal

This is making no sense to me what-so-ever. In either case you still have to instantiate a rect structure (or any structure for that matter), you still have to call a or more function, and verifying internals... isn't that something you would do during a debug step-through? not completely clear on that one.


Rect is too trivial.
Brokerage * b = getBrokerage();
b->fudge_the_numbers();
What happened in there? How many classes, how much magic? What if it needs to be updated? Do I even have the source for it?

Compare to:
double calculate_prime_index(vector<double> & d);
void hide_the_underperformers(vector<Account> & a, double index);
void print_fudged_report(vector<Account> & a);

struct Portfolio {
vector<Account> accounts;
vector<double> indices;
};

void fudge_the_numbers(Portfolio & p) {
double index = calculate_prime_index(p.accounts);
hide_the_underpreformers(p.accounts, index);
print_fudged_report(p.accounts);
}


Not so infrequently, logic needs to be reused. In pure OO way, one would need to override the class, hope that proper functions are virtual, and hope that extending it won't break anything. Or replace the original class, which may break other applications.

With free functions, just reuse parts of it, and combine them into new result.

Given such clear functions, I don't even need the source - I can reuse all the original functions, just write my new fudge_the_numbers. That simple isn't possible under C++ model.

Ruby has stuff like that though, and while it can cause a huge mess, it's quite useful in practice.

It's may not seem relevant if working with pure open source, on fresh projects, with no other interference (requirements, team members, etc...).

PS: I don't work in finance....

Share this post


Link to post
Share on other sites
Sorry ive been editing my post some, and you may have missed this part;

My post:
Quote:
In my opinion, a free function should be prefered when the need arises to wrap existing functionality into a unified interface without cluttering up the class definition.


Which is, in essence, what you are saying here;

Your post:
Quote:
With free functions, just reuse parts of it, and combine them into new result.


Now that makes perfect sense to me, but the way Mike Meyers presents it did not, I guess.

Correct?

Share this post


Link to post
Share on other sites
Another way to think about it is this. Structures and classes are fundamentally a way to organize data, and provide operations on that data. Free functions are fundamentally a way to organize logic.

Consider std::sort for instance. If each container type had to sort itself internally, you would have many trivial variations on the basic sort algorithm implemented by std::sort. If you find a bug in the algorithm, or want to tweak it for performance (most implementations of the standard library use some hybrid sort algorithms to try and mitigate the weaknesses of certain approaches to sorting) then you have to go fix all those implementations.

Instead, you provide an interface from the containers themselves (i.e. iterators) which you can use to manipulate all containers in a uniform way. Thusly, your sort algorithm needs only one implementation, and all conforming containers (including non-standard containers, such as ones from Boost, or ones you write yourself) can benefit from the existence of the algorithm. Thanks to some magic in the way C++ templates work, and thanks to good optimizing compilers, this actually generates basically the same code as if you had written a sort algorithm for every single container, without you having to maintain all that code by hand.

If you run into a case where a container really can do a better job by sorting itself internally rather than using the public algorithm, there's always the option of adding a member function, as std::list does.


So you can think of free functions as a way to abstract logic away from the type of data it operates on. Another important possibility is that you may have logic that doesn't really make sense to "attach" to a particular piece of data, and therefore doesn't make sense to have in a structure/class interface.

For instance, our game's large-scale pathfinding routine needs three basic parameters: a starting and ending waypoint, and a "traveler" description. A waypoint is just a set of coordinates relative to some object. A traveler stores information like the size and speed of an object, what types of routes/maneuvers it is likely to prefer, and so on.

The waypoints need not be relative to the same object - in fact, they almost never are, because most of the interesting pathing needs to be done across the game world. So attaching the pathfinding logic to the waypoint or object doesn't make sense.

The traveler itself doesn't know anything about path data, or where other travelers are; this means it is also a poor candidate for where to attach the pathing logic. The pathfinding algorithm would have to access all the path and obstruction data from some external source anyways, meaning there's really no benefit to associating it with the traveler.

There isn't a single "world" object that tracks all of the information of what paths are available, what paths are blocked, and so on; that detail is split up across potentially hundreds of different subdivisions of the game space, in different formats and different levels of detail depending on the size and granularity of the division in question. All that data needs to be aggregated in some way to find a path across the game world, but it is used by so many things that it doesn't make sense to have a giant "everything navigation data" object - that would gather too much functionality into a single place and just make things messy and hard to maintain.

So voila - make the pathfinding a free function, and give it an interface to each element of data it needs to talk to, and problem solved.



Like most software design questions, knowing when to use member vs. free functions is not really something you can manage with a set of fast-and-loose rules. Much of the time there really isn't one "right" or even "better" way to go; it just depends on what works best for you at the time, or even just your whims as to how you want things to be organized. It takes some practice and experience to develop good instincts, so don't worry if it isn't always crystal clear what "should" be done in any given situation.

Share this post


Link to post
Share on other sites
Quote:
Original post by ApochPiQ
Another way to think about it is this. Structures and classes are fundamentally a way to organize data, and provide operations on that data. Free functions are fundamentally a way to organize logic.

Consider std::sort for instance. If each container type had to sort itself internally, you would have many trivial variations on the basic sort algorithm implemented by std::sort. If you find a bug in the algorithm, or want to tweak it for performance (most implementations of the standard library use some hybrid sort algorithms to try and mitigate the weaknesses of certain approaches to sorting) then you have to go fix all those implementations.

Instead, you provide an interface from the containers themselves (i.e. iterators) which you can use to manipulate all containers in a uniform way. Thusly, your sort algorithm needs only one implementation, and all conforming containers (including non-standard containers, such as ones from Boost, or ones you write yourself) can benefit from the existence of the algorithm. Thanks to some magic in the way C++ templates work, and thanks to good optimizing compilers, this actually generates basically the same code as if you had written a sort algorithm for every single container, without you having to maintain all that code by hand.

If you run into a case where a container really can do a better job by sorting itself internally rather than using the public algorithm, there's always the option of adding a member function, as std::list does.


So you can think of free functions as a way to abstract logic away from the type of data it operates on. Another important possibility is that you may have logic that doesn't really make sense to "attach" to a particular piece of data, and therefore doesn't make sense to have in a structure/class interface.

For instance, our game's large-scale pathfinding routine needs three basic parameters: a starting and ending waypoint, and a "traveler" description. A waypoint is just a set of coordinates relative to some object. A traveler stores information like the size and speed of an object, what types of routes/maneuvers it is likely to prefer, and so on.

The waypoints need not be relative to the same object - in fact, they almost never are, because most of the interesting pathing needs to be done across the game world. So attaching the pathfinding logic to the waypoint or object doesn't make sense.

The traveler itself doesn't know anything about path data, or where other travelers are; this means it is also a poor candidate for where to attach the pathing logic. The pathfinding algorithm would have to access all the path and obstruction data from some external source anyways, meaning there's really no benefit to associating it with the traveler.

There isn't a single "world" object that tracks all of the information of what paths are available, what paths are blocked, and so on; that detail is split up across potentially hundreds of different subdivisions of the game space, in different formats and different levels of detail depending on the size and granularity of the division in question. All that data needs to be aggregated in some way to find a path across the game world, but it is used by so many things that it doesn't make sense to have a giant "everything navigation data" object - that would gather too much functionality into a single place and just make things messy and hard to maintain.

So voila - make the pathfinding a free function, and give it an interface to each element of data it needs to talk to, and problem solved.



Like most software design questions, knowing when to use member vs. free functions is not really something you can manage with a set of fast-and-loose rules. Much of the time there really isn't one "right" or even "better" way to go; it just depends on what works best for you at the time, or even just your whims as to how you want things to be organized. It takes some practice and experience to develop good instincts, so don't worry if it isn't always crystal clear what "should" be done in any given situation.


Ah, now that does make sense. :P
I guess I was viewing the item from the wrong perspective as it never crossed my mind to consider these situations.

Thanks for clearing that up :)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!