There are things that are objects. Things that have state and change their state are objects. And then there are things that are not objects. A binary search is not an object. It is an algorithm.
Alex Stepanov
Introduction
Most of us who use object oriented languages tend to think that non-member functions are a procedural programming tool that has no place in OOP. In this article, I will try to show that non-member functions are still very useful, and that by using non-member functions you can improve design of your OO code. This is by no means a criticism of object oriented programming in general. On the contrary, my aim is to show that non-member functions can fit into OO design just fine. I believe that careful use of non-member function can:
- make the code more readable and maintainable
- improve data encapsulation
- reduce class coupling
Note that I never talk about global functions here. For anything but the very small projects, I strongly advocate use of namespaces in order to avoid global namespace pollution and name clashes.
Non-member functions in action
Contrary to what "OO-purists" would like us to believe, not everything is an object. Yes, when you have some data, and some operations on that data, it makes perfect sense to wrap them into a class. However, there are operations that deal with different data at the same time, and in that case it may be more logical to implement this operation as a non-member function.
Readability
Let's illustrate my point with an example:
class sample_string
{
std::vector<char> data_;
public:
explicit sample_string (const char* cstr);
int get_length() const;
char get_at (int index) const;
void append(const sample_string& other);
sample_string concat (const sample_string& other);
};
We are going to discuss which of the member functions should be non-members. But, before we do that, let's try to answer the ultimate question: Why did we make class sample_string at all?. To make our program "more object oriented"? Wrong! The primary reason for making this class was to protect our data from improper usage. The secondary reason was to make string operations more logical and readable through coupling the data with operations that handle it.
Back to our sample. Our data is made private, and this is good. Now we are in charge of how the data is accessed, and this was the primary goal of making this class. Now, what about member functions?
get_length without any doubt belongs to the class. It retrieves information about object's state, and to do so, it needs read-only access to class internal implementation. All of that can be concluded by a glance at the function's signature. The same goes for get_at.
If we look at the way function append is used, we can see that this function also belongs to the class sample_string:
sample_string a , b;
...
a.append(b);
The syntax suggests us that we alter the state of object a with some information from object b, and that's exactly what happens. The call to function append affects only the state of object a and logically belongs with the data of that object.
Now, the function concat. It is used like this:
sample_string a, b;
...
sample_string c = a.concat(b);
By looking at the syntax, we could come to the wrong conclusion that this function somehow alters the state of object a. All that it does, though, is making a brand new object c based on the data from both a and c. More, all the data needed for creation of object c can be acquired through other public members, which means that this function does not really need direct access to class data.
Let's implement this operation as a non-member function.:
sample_string concat (const sample_string& left,
const sample_string& right);
This operation would be used like this:
sample_string a, b;
...
sample_string c = concat(a, b);
This time, the syntax suggests that we used some information from objects a and b to generate object c, and that's exactly what we did. Code better reflects our intent, and therefore is more logical and readable.
Examples like this can be found in many real-life libraries and applications. For instance, take a look at .NET FCL System.String class. It is immutable, but member functions such as Insert, Trim, Replace really suggest that they alter the state of a string. If .NET supported non-member functions and constant arguments, function Insert could be rewritten like this (MC++ syntax):
String __gc* Insert (const String __gc* source, int startIndex,
const String __gc* value);
While the name of the function is still somewhat misleading, the signature leaves no doubt what really happens.
Encapsulation
What about encapsulation? A lot has been written about how the usage of non-member functions improves encapsulation, but it comes down to this simple statement: non-member functions cannot access internal class implementation. If you can implement a functionality without messing with internal implementation, then it means better encapsulation.
Of course, this does not mean that we should try to implement each functionality as a non-member function in order to improve encapsulation. Sometimes a function needs direct access to internal class data, and in that case it may be necessary to implement it as a member. Even if a function needs read-only access to class data, in many cases it is a better idea to implement it as a member function. Introducing an accessor function to internal class data in order to implement a function as a non-member, is almost always a bad idea. For instance, in our example with class sample_string it was OK to implement function concat as a non-member because it can be implemented by using existing public functions get_length and get_at. However, if we didn't have those two functions, we would need to introduce a const accessor in order to make concat a non-member. If this accessor returns a copy of class data, then we are probably OK.
std::vector<char> sample_db::get_data() const
{
return data_;
}
Of course, it can be unacceptable to copy data like this in terms of performance, but if we talk about encapsulation, this is a good solution.
However, if the accessor returns a reference or a pointer to the data, then we actually hurt encapsulation.
const std::vector<char>& sample_db::get_data() const
{
return data_;
}
If we do that, we expose our class data directly to the class users, and that is bad. Imagine that at some point we decide to change internal class data to be std::string rather than std::vector<char>. In that case all the functions that use get_data must be rewritten. Implementing concat as a member function would hurt encapsulation much less than adding an accessor like this. In some cases, it might be a good solution to implement concat as a friend non-member function. Of course, it wouldn't improve encapsulation, but it would still be more readable than a version with member function.
Coupling
Finally, let's see how the use of non-member functions affects object coupling. It is a good design practice to promote loose coupling between objects. The less objects know about each other, the better. On the other hand, objects need to interact in order to make system functional. To solve this problem, Mediator Design Pattern has been introduced. Mediators are classes that handle interaction between different types and thus promote loose coupling. However, there are cases when a non-member function is enough to serve the role of a mediator. For instance, assume we have a class sample_db that wraps a database engine:
class sample_db {...};
Now, imagine that in an application of ours we need to write sample_string objects to a sample_db database. The first thing that may come to our mind would be to add a function to sample_db that would take care of that:
class sample_db
{
...
public:
void write_sample_string (const sample_string& str);
};
That would work, but it requires sample_db to know about sample_string, and that is bad. If we want to reuse sample_db in some other project, we would need to include sample_string even if we don't need it. A better approach would be to make a non-member function in a separate compilation unit that would serve as a mediator between those two classes:
void write_sample_string_to_db (const sample_string& str, sample_db& db);
Of course, in order to make this work, sample_db would need to have a public function that writes some form of data to a database.
Political incorrectness of non-member functions
So far, we have discussed only the technical side of the problem, and in ideal world that would be enough. Alas, we live in real world, and if you decide to use non-member functions in our programs, you may experience other kinds of problems. Namely, many people still think that non-member functions are a legacy from C that has no place in object oriented programming practices. This can raise several issues:
- During code reviews, your code may be attacked as "not object oriented". This can be particularly inconvenient if you are a junior developer, or relatively new in your organization. On the other hand, if you are already established and respected within your team, you can make use of code reviews to introduce the benefits of non-member functions to your co-workers.
- If your company ships the source to customers, some of them may also question the quality of your code from the same perspective. Again, in some cases you may be able to explain them the reasons for using non-member functions, but that depends on your relationship with the client, and the level of trust they have in you.
Conclusion
I hope this article will make some developers reconsider use of non-member functions in their OO programs. However, the last thing I want is to turn anybody from a "OO zealot" to a "non-member functions zealot". Be aware that there are cases when use of non-member functions makes sense, and cases when it does not. Use your knowledge, experience and instinct to decide when to implement operations as member functions and when to leave them out of classes. Always bear in mind that your ultimate goal is to produce high-quality code, and for that purpose use any programming technique that you believe will be appropriate for your specific tasks. Don't be a slave to any programming paradigm. Make them serve you.
References
- Bjarne Stroustrup: "The C++ Programming Language", Addison-Wesley Pub Co; ISBN: 0201889544 ; 3rd edition (June 20, 1997)
- Scott Meyers, How Non-Member Functions Improve Encapsulation
| You must Sign In to use this message board. |
|
|
 |
|
 |
Zero, OOP is not just about data encapsulation. It's even more about *knowledge* encapsulation!
First, you never refer to the word "static" in your article, you can use the browser's find function to check that.
Second, there is NO better place to put the 'concat' function than as a public static method of the class itself. Why?
1) What namespace should I put the non-member 'concat' in? string_tools ? string_ops ? That's redundant!
2) If I put the non-member function 'concat' outside of the class itself, I break the "knowledge encapsulation" about the class. Why should someone else know better how to concatenate strings than the string class itself?
3) When I see string::concat(a, b) I immediately know where to search for its implementation!
4) For reasons like the first 3 (and more, I guess) newer and higher-level object-oriented platforms like Java and .NET do not provide such an idiom.
If you think about it, it makes much much more sense NOT to have global (namespaced non-member functions global too) functions. Think of a function as a 'knowledge' about something. In the real world, there is no global knowledge - it is always associated with something. A knowledge taken alone, doesn't makes sense. It has to have some context. Classes and namespaces provide a mechanism of narrowing the knowledge to some scope. And the smaller the scope, the more independent the modules are.
Following this logic, reason 2) makes the strongest argument here.
Now, I agree that the .NET String members Insert, Trim and others should not be declares as they are. But it makes *much* more sense to make them static member functions, for example:
String String.Trim(String str);
so that I write:
String newStr = String.Trim(someOtherSting);
The wonderful thing here is that, as you said, we don't make wrong conclusions, AND the knowledge encapsulation it not ruined.
Finally, static members functions ARE "ok". What is a hack are the friend functions and classes. Not the other way around.
That is why, I strongly argue that there is absolutely no good reason of using non-member functions for manipulating objects, except for the built-in types.
|
| Sign In·View Thread·PermaLink | 3.22/5 |
|
|
|
 |
|
 |
iliyang wrote: Zero, OOP is not just about data encapsulation. It's even more about *knowledge* encapsulation!
What do you mean by *knowledge*?
iliyang wrote: Second, there is NO better place to put the 'concat' function than as a public static method of the class itself.
Static methods are equivalent to nonmember friends, as stated below. Nonmember-nonfriends provide better encapsulation.
iliyang wrote: What namespace should I put the non-member 'concat' in? string_tools ? string_ops ? That's redundant!
How about string?
iliyang wrote: If I put the non-member function 'concat' outside of the class itself, I break the "knowledge encapsulation" about the class. Why should someone else know better how to concatenate strings than the string class itself?
You totally miss the point here. The point is that the concat function does not need to know how to concatenate strings, and therefore, by good encapsulation design, it shouldn't.
iliyang wrote: When I see string::concat(a, b) I immediately know where to search for its implementation!
The nonmember functions discussed here are part of the class' public interface and should be documented as such.
iliyang wrote: For reasons like the first 3 (and more, I guess) newer and higher-level object-oriented platforms like Java and .NET do not provide such an idiom.
But they do! In Java, for instance, put a static function inside a different class (to mimic a namespace), and you have the same effect.
iliyang wrote: If you think about it, it makes much much more sense NOT to have global (namespaced non-member functions global too) functions. Think of a function as a 'knowledge' about something. In the real world, there is no global knowledge - it is always associated with something. A knowledge taken alone, doesn't makes sense. It has to have some context. Classes and namespaces provide a mechanism of narrowing the knowledge to some scope. And the smaller the scope, the more independent the modules are.
I totally agree with you here. But this is an argument FOR nonmember-nonfriend functions, NOT AGAINST them! You make the scope smaller by hiding class internals from functions which don't need to access them.
iliyang wrote: Finally, static members functions ARE "ok". What is a hack are the friend functions and classes. Not the other way around.
As stated below, static member functions and friend nonmember functions are semantically the same.
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
my suggestion over here probably is more towards to my own preference, it will still maintain OO looks.
class sample_string { std::vector data_; public: explicit sample_string (const char* cstr); int get_length() const; char get_at (int index) const; void append(const sample_string& other); static sample_string concat (const sample_string& other1, const sample_string& other2); };
using it,
sample_string a,b,c; .. c = sample_string::concat(a,b);
from, -= aLbert =-
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Just as you said: it only looks like OO . Static functions are really equivalent to non-member friend functions. If you don't need access to the private data, non-members are often a cleaner solution.
My programming blahblahblah blog. If you ever find anything useful here, please let me know to remove it.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
if u follow my way, u dun need to use namespace anymore. =) the function is enclosed inside the class.
from, -= aLbert =-
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Yes, static members have the scope of the class. But I usually put classes inside of namespaces as well. Ie:
namespace MyNamespace { class MyClass { }; void NonMember1 (MyClass& myObject, ); void NonMember2 (MyClass& myObject, ); }
My programming blahblahblah blog. If you ever find anything useful here, please let me know to remove it.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
you wrote: But I usually put classes inside of namespaces as well. Ie: i know. but... see below...
MyClassA goes here...
namespace MyModule { class MyClassA { }; void NonMember1RelatedToMyClassA (MyClassA& myObject, ); void NonMember2RelatedToMyClassA (MyClassA& myObject, ); }
MyClassB goes here...
namespace MyModule { class MyClassB { }; void NonMember1RelatedToMyClassB (MyClassB& myObject, ); void NonMember2RelatedToMyClassB (MyClassB& myObject, ); }
All non-member function will be inside MyModule, a bit messy.  If you want to browse them, static function is always better.  All our non-member function is put inside class and declared static.
from, -= aLbert =-
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I'd like to start by saying great article with interesting discussion.
My opinion ... ? You have to seperate what is right and wrong from personal preference and formal correctness. I also use statics as mentioned but I know this to be wrong 
My workplace has a very formal design modal [standards / best practices make up the rules we design and write code by for formal review ] - The points mentioned in this article are also mentioned in some of my code reviews by the (suppossed) OO guru's ... BUT ... I prefer the visual appearence and functional grouping of members to "external / outside" declarations ... even though when looking at class design this is bad practice, and with OOP just plain wrong 
Baring in mind we should treat the design of objects and classes differently, although they are merely seperated by concept, this can be a difficult goal to achieve.
I try to balance this by making my own rule :- If the class is to be re-used and in effect, made into an object to be manuipulated by other classes ... or perhaps there maybe 100's + instances, then it must have loose coupling (or med's) to aid in better and cleaner excapsulation. Else ... It can contain my member functions and look nicer to me 
Compromise ... ? ... or rubbish ?
STeve
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hello Steve
Thanks for your comments.
steve_hsk wrote: If the class is to be re-used and in effect, made into an object to be manuipulated by other classes ... or perhaps there maybe 100's + instances, then it must have loose coupling (or med's) to aid in better and cleaner excapsulation. Else ... It can contain my member functions and look nicer to me
Interesting point. However, I wouldn't stick to any "hard-coded" rules like this. One size does not fit all, and you need to decide on per-cases basis.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
friend functions don't break encapsulation because class writer detects who will be his friends. BTW great article
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hello Mohammad,
Thanks for your comments.
mohammad tayseer wrote: friend functions don't break encapsulation because class writer detects who will be his friends
Yes, I see your point. However, if internal class implementation is changed, friends might be broken, and in that sense friends hurt encapsulation.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
hello Nemanja friends can be considered as a member function with special syntax for simplicity. You said that too in you article
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
mohammad tayseer wrote: friends can be considered as a member function with special syntax for simplicity
Yes, you are right. Friends are the same as member functions (actually, the calling convention is somewhat diferrent - when calling a member function, this is usually put in register ecx), but the point is that member functions are the ones who hurt encapsulation. If you can implement a function as a non-friend non-member, that will improve encapsulation. If you can't, it really doesn't matter whether you use a member or a friend nonmember; it is the same.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I will occasionally use non-member functions, but for different reasons. I tend to use non-member functions or things that don't neatly fit in any one class or for global functions (rare, but still occasionally necessary.)
For member functions, my criteria seem to be a little different from yours. If a function acts only on a certain class, and is specific to that class, I make it a member function. It may be able to do all its work by using existing member functions, so I don't care if it has to access private data or not. For example, the database example you had where you had a member function taking a string class, I would have left that as a member function. Who cares if it needs the string class? I'll just include them both in the same library and let the linker figure it out. Only for really hairy cases, or when dependencies make a big difference, would I care about that.
And for the other examples you show, I almost always use static member functions for those. That way you get the best of both worlds - they're part of the class, but don't access private data in individual class objects. 
"When a man sits with a pretty girl for an hour, it seems like a minute. But let him sit on a hot stove for a minute and it's longer than any hour. That's relativity." - Albert Einstein
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Navin wrote: For example, the database example you had where you had a member function taking a string class, I would have left that as a member function. Who cares if it needs the string class? I'll just include them both in the same library and let the linker figure it out. Only for really hairy cases, or when dependencies make a big difference, would I care about that.
Well, if you don't care about loose object coupling, then you are right. However, it is generally accepted that loose coupling is a principle of good design. In the article, I argue that mediator classes can sometimes be replaced by non-member functions, which is simpler and cleaner.
Navin wrote: And for the other examples you show, I almost always use static member functions for those. That way you get the best of both worlds - they're part of the class, but don't access private data in individual class objects.
Uhm, static member functions can access private data - that's one reason why I don't use them in this context. I usually use static member functions for object factories when I need to be in control how objects are created, or to prevent deriving from my class.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Nemanja Trifunovic wrote: Well, if you don't care about loose object coupling, then you are right. However, it is generally accepted that loose coupling is a principle of good design. In the article, I argue that mediator classes can sometimes be replaced by non-member functions, which is simpler and cleaner.
Well, as far as loose coupling, I've always been under the belief that it really depends on the clases. I guess your example isn't the best - a string object could potentially be used by lots of classes, and it would probably be a good design practice to use that as much as possible. If the two classes to be coupled are "big", then what you say makes sense, although I've rarely needed a whole separate class to mediate. Usually whatever object uses both of the classes ends up being the mediator, and what you would have as a non-member function would just be a function of another class.
Nemanja Trifunovic wrote: Uhm, static member functions can access private data - that's one reason why I don't use them in this context. I usually use static member functions for object factories when I need to be in control how objects are created, or to prevent deriving from my class.
I guess I was unclear on that one - they can only access private static members (which tend to be few.) Or private members of any objects you pass in as parameters. But they don't *have* to use private data.
I guess in short, I'm not sold. I sitll only use non-member functions for very specialized purposes and global functions that don't really fit anywhere else.
"When a man sits with a pretty girl for an hour, it seems like a minute. But let him sit on a hot stove for a minute and it's longer than any hour. That's relativity." - Albert Einstein
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Navin wrote: I guess your example isn't the best - a string object could potentially be used by lots of classes, and it would probably be a good design practice to use that as much as possible. If the two classes to be coupled are "big", then what you say makes sense, although I've rarely needed a whole separate class to mediate.
Yes, my example is far from being perfect, but I thought it can reflect the general idea: when a functionality needs to deal with more than one class, it can be a good idea to implement it as a non-member function and avoid unnecessary coupling. Of course, often this is not possible, and sometimes it is not desirable even when it is possible. However, there are cases when this should be done, and all I say is that in such cases you should just do it without feeling guilty.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
just wondering after reading Meyer's article:
It is possible to have the same namespace name for the non-member functions as the class name. I have not tried yet but if yes one could use the non static member functions similar to static members (almost similar).
MyClass obj; MyClass::callToNonMember(obj, param);
This would improve the code concerning the readability wouldn't it?
Timo
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Something I find myself doing frequently is if I have a function that is realted to a class, but doesn't need any of the class data (or only needs static data), then I make it a static function. It would look like this (stupid example, but it gets the point across):
class MyClass { public: int Func1(); int Func2();
static int Func3(MyClass a, MyClass b); private: int data1, data2; };
... int MyClass::Func3(MyClass a, MyClass b) { return a.Func1() + b.Func2(); }
In code that I write, I tend to use static member functions - for this reason or for other reasons - much more than non-member functions, although the two are similar in many ways.
"When a man sits with a pretty girl for an hour, it seems like a minute. But let him sit on a hot stove for a minute and it's longer than any hour. That's relativity." - Albert Einstein
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
Static functions can access the private part of the class declaration, and therefore are less desirable than non-member functions. They are more similar to friends non-members, except of course they are in the scope of the class.
For this reason, I think that idea with namespaces is often better
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Nemanja Trifunovic wrote: Static functions can access the private part of the class declaration, and therefore are less desirable than non-member functions.
Actually, I think that makes them *more* desirable. OO isn't just about data hiding.. it's also about creating objects that make sense. And static members beat non-members hands down when access to private data is actually needed... you remove the need to create artificial accessor functions.
And just becuase you have access to private data doesn't mean you have to use it directly or depend on it.
"When a man sits with a pretty girl for an hour, it seems like a minute. But let him sit on a hot stove for a minute and it's longer than any hour. That's relativity." - Albert Einstein
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Navin wrote: And static members beat non-members hands down when access to private data is actually needed... you remove the need to create artificial accessor functions.
I agree 100%, and I even wrote something like that in my article. However, if you can implement a functionality without introducing new accessors, than it is often a better idea to use a non-member functions. Thus you can be sure that you don't need to rewrite it if you change internal class implementation.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
 | Pozdrav  Srdjan Mladenovic | 15:35 14 Jul '03 |
|
 |
Moje ime je Srdjan Mladenovic rodjen sam u Leskovcu, apsolvent na Elektronskom fakultetu u Nisu (telekomunikacije). Trenutno radim na razvoju jedne aplikacije za upravljanje projektima nevladinih organizacija.To je nadgradnja za bazu podataka.Posto planiram dase i u buducnosti bavim razvojem slicnih aplikacija ,voleo bih da dobijem preporuku za neku dobru knjigu.Takodje me interesuje zastupljenost operativnih sistema i serverskih tehnologija za baze podataka na americkom trzistu. Ukoliko imas vremene voleo bih da dobijem odgovor. e-mail:smurfle@ptt.yu
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
99.9% of the time I will write a member function. However I think there are uses. In a single .cpp file I will use some Non-Member Functions to perform some internal work that may or may not be changed in the future. Many of these are template functions and only have a scope to that .cpp file.
In your last example if I expect to use sample_db in some other project where sample_string does not exist I will extend sample_db to sample_db_ex and add all non resuable functionality there or use multiple inheritance with templates.
One other thing that I do is I will rarely create a global Non-Member Function that is not in a namespace. This way there are no worries of a name collision.
John
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|