While puzzling with some facts on class design, specifically whether the functions should be members or not, I looked into Effective c++ and found Item 23, namely, Prefer no
Access to the book is by no mean necessary.
The issues we are dealing here are Dependency and Reuse.
In a well-designed software, you try to isolate items from one another so as to reduce Dependencies, because Dependencies are a hurdle to overcome when change is necessary.
In a well-designed software, you apply the DRY principle (Don't Repeat Yourself) because when a change is necessary, it's painful and error-prone to have to repeat it in a dozen different places.
The "classic" OO mindset is increasingly bad at handling dependencies. By having lots and lots of methods depending directly on the internals of the class, the slightest change implies a whole rewrite. It need not be so.
In C++, the STL (not the whole standard library), has been designed with the explicit goals of:
Therefore, the Containers expose well-defined interfaces that hide their internal representations but still offer sufficient access to the information they encapsulate so that Algorithms may be executed on them. All modifications are made through the container interface so that the invariants are guaranteed.
For example, if you think about the requirements of the sort
algorithm. For the implementation used (in general) by the STL, it requires (from the container):
Thus, any container that provides Random Access and is not Associative is (in theory) suitable to be sorted efficiently by (say) a Quick Sort algorithm.
What are the Containers in C++ that satisfy this ?
deque
vector
And any container that you may write if you pay attention to these details.
It would be wasteful, wouldn't it, to rewrite (copy/paste/tweak) sort
for each of those ?
Note, for example, that there is a std::list::sort
method. Why ? Because std::list
does not offer random access (informally myList[4]
does not work), thus the sort
from algorithm is not suitable.
Various thoughts:
friend
.object.function(x, y, z)
notation, which IMHO is very convenient, expressive and intuitive. They also work better with discovery/completion features in many IDE's.A separation as member and non-member functions can help communicate the essential nature of the class, it's invariants and fundamental operations, and logically group the add-on and possibly ad-hoc "convenience" features. Consider Tony Hoare's wisdom:
"There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult."
As non-member functionality expands in sophistication or picks up additional dependencies, the functions can be moved into separate headers and implementation files, even libraries, so users of the core functionality only "pay" for using the parts they want.
(Omnifarious's answer is a must-read, thrice if it's new to you.)
The motivation is simple: maintain a consistent syntax. As the class
evolves or is used, various non-member convenience functions will
appear; you don't want to modify the class interface to add something
like toUpper
to a string class, for example. (In the case of
std::string
, of course, you can't.) Scott's worry is that when this
happens, you end up with inconsistent syntax:
s.insert( "abc" );
toUpper( s );
By only using free functions, declaring them friend as needed, all functions have the same syntax. The alternative would be to modify the class definition each time you add a convenience function.
I'm not entirely convinced. If a class is well designed, it has a basic functionality, it's clear to the user which functions are part of that basic functionality, and which are additional convenience functions (if any such exist). Globally, string is sort of a special case, because it is designed to be used to solve many different problems; I can't imagine this being the case for many classes.
The criteria I use is if a function could be implemented significantly more efficiently by being a member function, then it should be a member function. ::std::sort
does not meet that definition. In fact, there is no efficiency difference whatsoever in implementing it externally vs. internally.
A vast efficiency improvement by implementing something as a member (or friend) function means that it greatly benefits from knowing the internal state of the class.
Part of the art of interface design is the art of finding the most minimal set of member functions such that all operations you might want to perform on the object can be implemented reasonably efficiently in terms of them. And this set should not support operations that shouldn't be performed on the class. So you can't just implement a bunch of getter and setter functions and call it good.
I think sort is not implemented as a member function because it's widely used, not only for vectors. If they had it as a member function, they'd have to re-implement it each time for each container using it. So I think it's for easier implementation.
So, first question, should not they be members than?
No, this doesn't follow. In idiomatic C++ class design (at least, in the idioms used in Effective C++), non-member non-friend functions extend the class interface. They can be considered part of the public API for the class, despite the fact that they don't need and don't have private access to the class. If this design is "not OOP" by some definition of OOP then, OK, idiomatic C++ is not OOP by that definition.
stretch the same reasoning to some other functions in vector class
That's true, there are some member functions of standard containers that could have been free functions. For example vector::push_back
is defined in terms of insert
, and certainly could be implemented without private access to the class. In that case, though, push_back
is part of an abstract concept, the BackInsertionSequence
, that vector implements. Such generic concepts cut across the design of particular classes, so if you're designing or implementing your own generic concepts that might influence where you put functions.
Certainly there are parts of the standard that arguably should have been different, for example std::string has way too many member functions. But what's done is done, and these classes were designed before people really settled down into what we now might call modern C++ style. The class works either way, so there's only so much practical benefit you can ever get from worrying about the difference.