Item 23 of Effective C++ states: Prefer non-member non-friend functions to member functions.
The whole purpose of the item was to encourage encapsulation, as well as package flexibility and functional extensibility, but my question is how far do you go when it comes to taking this advice?
For example, you could have your class, your private data members, and then take a minimalist approach by reducing public functions to only accessors and/or mutators for your private data members. Then, every single other function could be a non-member function.
However, would you be willing to increase encapsulation at the possible sacrifice of code clarity with accessors and mutators all over the place? Where is the line drawn?
First, not everyone agrees with this advice. I don't think I've seen anyone but Meyers (edit: and Herb Sutter) give this advice, and I've only seen it given within the context of C++. For example, creating "non-member non-friend functions" in Java or C# isn't really possible, since Java and C# have no free functions, and Ruby developers (for example) prefer "humane interfaces" that intentionally create member functions that do the same thing non-members could, just to make life easier on those functions' callers.
And even if you do accept Meyers' advice, that you should prefer non-member non-friend functions to member functions (and I think it's good advice, it certainly helped me apply encapsulation better to think of encapsulating a class's implementation even from its member functions), that's only one axis of design to consider.
The key concept of object-oriented design is that objects do something. An object isn't simply a bag of setters and getters that other code does stuff to. Instead, it should have behavior attached - that is, it should have methods that do things - and it should encapsulate the details of how it does those things. If you follow this approach to OO, then carrying Meyers' advice to the extreme as you did hurts encapsulation rather than helping it: you end up exposing all of the class's internal implementation variables via getters and setters instead of hiding them so that only the class's methods (the code responsible for doing stuff on behalf of the class, which is the only reason you have a class to begin with) can get to it.
So to answer your question of how far to take Meyers' advice: Don't needlessly turn functions into member functions if they could reasonably be implemented as non-friend non-member functions using a class's public interface, but don't damage a class's public interface and violate its encapsulation by exposing implementation just to avoid making something a member. And make sure you balance encapsulation against other concerns and other approaches (including, if your team decides to go that route, the pros and cons of a full-blown humane interface).
Take a step back and consider the purpose of the class: what one job is it doing? What class invariants must it ensure to do that one job optimally? What role do subclassing and overriding play in the class's purpose?
It's definitely not appropriate to cast everything in terms of accessors and mutators: that would nearly divorce the conjunction of state and behavior that is at the root of OOP, or mask behavior which in no sensible framing of the problem is about getting or setting attributes under the veil of such "pretended" mutators and accessors.
Classes with just accessors and mutators are one special case -- maybe one step up from traditional C-kind struct
s by being able to preserve some invariants, but "just barely";-).
Most classes in a good OOP design will have behavior -- and much as I like generic programming, one reason to use C++ is its strong mix of multiple paradigms, among which OOP must not be expunged!-)
Actually, providing only accessors and mutators to your class's private variables would in fact not be minimalist (or perhaps minimalist in one sense but not in the "most relevant" sense), as you are now presenting a more general interface to the world. The idea of encapsulation is that your class's interface should be as restricted as possible while allowing client code to get the job done.
Following this approach makes is easier to change the underlying implementation in the future, which is the point of encapsulation in the first place.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With