Templates and Factory Functions at Namespace Scope
In the previous section, I argued that static member functions should be made non-members whenever that is possible, because that increases class encapsulation. I consider these two possible implementations for a factory function:
// the less encapsulated design class Widget { ... public: static Widget* make(/* params */); }; // the more encapsulated design namespace WidgetStuff { class Widget { ... }; Widget* make( /* params */ ); };
Andrew Koenig pointed out that the first design (where make
is static inside the class) enables one to write a template function that invokes make
without knowing the type of what is being made:
template<typename T> void doSomething( /* params */ ) { // invoke T's factory function T *pt = T::make( /* params */ ); ... }
This isn't possible with the namespace-based design, because there's no way from inside a template to identify the namespace in which a type parameter is located. That is, there's no way to figure out what ???
is in the pseudocode below:
template<typename T> void doSomething( /* params */ ) { // there's no way to know T's containing namespace! T *pt = ???::make( /* params */ ); ... }
For factory functions and similar functions which can be given uniform names, this means that maximal class encapsulation and maximal template utility are at odds. In such cases, you have to decide which is more important and cater to that. However, for static member functions with class-specific names, the template issue fails to arise, and encapsulation can again assume precedence.
Syntax Issues
If you're like many people with whom I've discussed this issue, you're likely to have reservations about the syntactic implications of my advice that non-friend non-member functions should be preferred to member functions, even if you buy my argument about encapsulation. For example, suppose a class Wombat
supports the functionality of both eating and sleeping. Further suppose that the eating functionality must be implemented as a member function, but the sleeping functionality could be implemented as a member or as a non-friend non-member function. If you follow my advice from above, you'd declare things like this:
class Wombat { public: void eat(double tonsToEat); ... }; void sleep(Wombat& w, double hoursToSnooze);
That would lead to a syntactic inconsistency for class clients, because for a Wombat
w
, they'd write
w.eat(.564);
to make it eat, but they would write
sleep(w, 2.57);
to make it sleep. Using only member functions, things would look much neater:
class Wombat { public: void eat(double tonsToEat); void sleep(double hoursToSnooze); ... }; w.eat(.564); w.sleep(2.57);
Ah, the uniformity of it all! But this uniformity is misleading, because there are more functions in the world than are dreamt of by your philosophy.
To put it bluntly, non-member functions happen. Let us continue with the Wombat
example. Suppose you write software to model these fetching creatures, and imagine that one of the things you frequently need your Wombat
s to do is sleep for precisely half an hour. Clearly, you could litter your code with calls to w.sleep(.5)
, but that would be a lot of .5
s to type, and at any rate, what if that magic value were to change? There are a number of ways to deal with this issue, but perhaps the simplest is to define a function that encapsulates the details of what you want to do. Assuming you're not the author of Wombat
, the function will necessarily have to be a non-member, and you'll have to call it as such:
// might be inline, but it doesn't matter void nap(Wombat& w) { w.sleep(.5); } Wombat w; ... nap(w);
And there you have it, your dreaded syntactic inconsistency. When you want to feed your wombats, you make member function calls, but when you want them to nap, you make non-member calls.
If you reflect a bit and are honest with yourself, you'll admit that you have this alleged inconsistency with all the nontrivial classes you use, because no class has every function desired by every client. Every client adds at least a few convenience functions of their own, and these functions are always non-members. C++ programers are used to this, and they think nothing of it. Some calls use member syntax, and some use non-member syntax. People just look up which syntax is appropriate for the functions they want to call, then they call them. Life goes on. It goes on especially in the STL portion of the Standard C++ library, where some algorithms are member functions (e.g., size
), some are non-member functions (e.g., unique
), and some are both (e.g., find
). Nobody blinks. Not even you.
Interfaces and Packaging
Herb Sutter has explained that the "interface" to a class (roughly speaking, the functionality provided by the class) includes the non-member functions related to the class, and he's shown that the name lookup rules of C++ support this meaning of "interface." This is wonderful news for my "non-friend non-members are better than members" argument, because it means that the decision to make a class-related function a non-friend non-member instead of a member need not even change the interface to that class! Moreover, the liberation of the functions in a class's interface from the confines of the class definition leads to some wonderful packaging flexibility that would otherwise be unavailable. In particular, it means that the interface to a class may be split across multiple header files.
Suppose the author of the Wombat
class discovered that Wombat
clients often need a number of convenience functions related to eating, sleeping, and breeding. Such convenience functions are by definition not strictly necessary. The same functionality could be obtained via other (albeit more cumbersome) member function calls. As a result, and in accord with my advice in this article, each convenience function should be a non-friend non-member. But suppose the clients of the convenience functions for eating rarely needed the convenience functions for sleeping or breeding. And suppose the clients of the sleeping and breeding convenience functions also rarely needed the convenience functions for eating and, respectively, breeding and sleeping.
Rather than putting all Wombat
-related functions into a single header file, a preferable design would be to partition the Wombat
interface across four separate headers, one for core Wombat
functionality (primarily the class definition), and one each for convenience functions related to eating, sleeping, and breeding. Clients then include only the headers they need. The resulting software is not only clearer, it also contains fewer gratuitous compilation dependencies. This multiple-header approach was adopted for the standard library. The contents of namespace std
are spread across 50 different headers. Clients #include
the headers declaring the parts of the library they care about, and they ignore everything else.