views:

195

answers:

5

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?

A: 

In general the idea behind the accessors an mutators is that the variable needs to be guarded in some way. if the variable has sensitive information that is supposed to be updated by only one entity, you can include that check in your mutator. The simple fact is that most accessors and mutators are simply a formality (so much so that C# now has automatic properties) and are not necessary. The bottom line is, use your judgment. if it needs restricted access, then add a mutator. If it doesnt matter who gets / sets the variable, the accessor and mutator aren't needed.

Scott M.
Vitali
+2  A: 

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 structs 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!-)

Alex Martelli
+1  A: 

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.

j_random_hacker
+8  A: 

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).

Josh Kelley
Herb Sutter agrees with Meyers, and provides a better example: http://www.gotw.ca/gotw/084.htm
Mark Ransom
The rules are obviously stated in such a straightforward manner so that they'll stick with the reader, but I think they are all to be taken with a grain of salt. The question was more open-ended and didn't really reflect personal feelings. I just thought it was interesting to consider, and this post definitely cleared that up by clarifying the purpose of OOP (the post was tagged C++ so I figured that was what would be considered). P.S. I don't have a team, I'm not even eligible to work full time.
trikker
A: 

Working on how to package things into right sized units is a subjective art.

One of the issues I have with making something non-member non-friend is that if a change in internal structure requires that new public interfaces be added to support existing non-members or that the non-member now be acknowledged as a friend you are showing a tighter linkage than was previously devised. It may now become costly to make any change.

While it can be said at a logical level a non-member function can be packaged with the class and make up the class' interface, the developer will need to make some code change if the location of a method changes, and so the implementation of the interface is not, errr, transparent in it's use.

One of those nice things about Eiffel, a method with no parameters is accessed in the same way as a variable, and so changing between those elements is transparent.

Greg Domjan