In the (otherwise) excellent book C++ Coding Standards, Item 44, titled "Prefer writing nonmember nonfriend functions", Sutter and Alexandrescu recommend that only functions that really need access to the members of a class be themselves members of that class. All other operations which can be written by using only member functions should not be part of the class. They should be nonmembers and nonfriends. The arguments are that:
Although I see the value in these argument, I see a huge drawback: my IDE can't help me find these functions! Whenever I have an object of some kind, and I want to see what operations are available on it, I can't just type "pMysteriousObject->
" and get a list of member functions anymore.
Keeping a clean design is in the end about making your programming life easier. But this would actually make mine much harder.
So I'm wondering if it's really worth the trouble. How do you deal with that?
Scott Meyers has a similar opinion to Sutter, see here.
He also clearly states the following:
"Based on his work with various string-like classes, Jack Reeves has observed that some functions just don't "feel" right when made non-members, even if they could be non-friend non-members. The "best" interface for a class can be found only by balancing many competing concerns, of which the degree of encapsulation is but one."
If a function would be something that "just makes sense" to be a member function, make it one. Likewise, if it isn't really part of the main interface, and "just makes sense" to be a non-member, do that.
One note is that with overloaded versions of eg operator==(), the syntax stays the same. So in this case you have no reason not to make it a non-member non-friend floating function declared in the same place as the class, unless it really needs access to private members (in my experience it rarely will). And even then you can define operator!=() a non-member and in terms of operator==().
I don't think it would be wrong to say that between them, Sutter, Alexandrescu, and Meyers have done more for the quality of C++ than anybody else.
One simple question they ask is:
If a utility function has two independent classes as parameteres, which class should "own" the member function?
Another issue, is you can only add member functions where the class in question is under your control. Any helper functions that you write for std::string will have to be non-members since you cannot re-open the class definition.
For both of these examples, your IDE will provide incomplete information, and you will have to use the "old fashion way".
Given that the most influential C++ experts in the world consider that non-member functions with a class parameter are part of the classes interface, this is more of an issue with your IDE rather than the coding style.
Your IDE will likely change in a release or two, and you may even be able to get them to add this feature. If you change your coding style to suit todays IDE you may well find that you have bigger problems in the future with unextendable/unmaintainable code.
I'm going to have to disagree with Sutter and Alexandrescu on this one. I think if the behavior of function foo()
falls within the realm of class Bar
's responsibilities, then foo()
should be part of bar()
.
The fact that foo()
doesn't need direct access to Bar
's member data doesn't mean it isn't conceptually part of Bar
. It can also mean that the code is well factored. It's not uncommon to have member functions which perform all their behavior via other member functions, and I don't see why it should be.
I fully agree that peripherally-related functions should not be part of the class, but if something is core to the class responsibilities, there's no reason it shouldn't be a member, regardless of whether it is directly mucking around with the member data.
As for these specific points:
It promotes encapsulation, because there is less code that needs access to the internals of a class.
Indeed, the fewer functions that directly access the internals, the better. That means that having member functions do as much as possible via other member functions is a good thing. Splitting well-factored functions out of the class just leaves you with a half-class, that requires a bunch of external functions to be useful. Pulling well-factored functions away from their classes also seems to discourage the writing of well-factored functions.
It makes writing function templates easier, because you don't have to guess each time whether some function is a member or not.
I don't understand this at all. If you pull a bunch of functions out of classes, you've thrust more responsibility onto function templates. They are forced to assume that even less functionality is provided by their class template arguments, unless we are going to assume that most functions pulled from their classes is going to be converted into a template (ugh).
It keeps the class small, which in turn makes it easier to test and maintain.
Um, sure. It also creates a lot of additional, external functions to test and maintain. I fail to see the value in this.
It's true that external functions should not be part of the interface. In theory, your class should only contain the data and expose the interface for what it is intended and not utilitarian functions. Adding utility functions to the interface just grow the class code base and make it less maintainable. I currently maintain a class with around 50 public methods, that's just insane.
Now, in reality, I agree that this is not easy to enforce. It's often easier to just add another method to your class, even more if you are using an IDE that can really simply add a new method to an existing class.
In order to keep my classes simple and still be able to centralize external function, I often use utility class that works with my class, or even namespaces. I start by creating the class that will wrap my data and expose the simplest possible interface. I then create a new class for every task I have to do with the class.
Example: create a class Point, then add a class PointDrawer to draw it to a bitmap, PointSerializer to save it, etc.
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