Sponsored Link •
Bill Venners: You said that the invariant helps you decide what goes into the interface. Could you elaborate on how? Let me attempt to restate what you said, and see if I understand it. The functions that are taking any responsibility for maintaining the invariant should be in the class.
Bjarne Stroustrup: Yes.
Bill Venners: Anything that's just using the data, but not defending the invariant, doesn't need to be in the class.
Bjarne Stroustrup: Let me give an example. There are some operations you
really can't do without having direct access to the representation. If you have an
operation that changes the size of a
vector, then you'd better be able to make changes to
the number of elements stored. You move the elements and change the size variable.
If you've just got to read the size variable, well, there must be a member function for that.
But there are other functions that can be built on top of existing functions.
For example, given efficient element access, a find function for
searching in a vector is best provided as a non-member.
Another example would be a
Date class, where the operations that actually change the
day, month, and year have to be members. But the function that finds the next weekday,
or the next Sunday, can be put on top of it. I have seen
Date classes with 60 or 70
operations, because they built everything in. Things like
Functions like that don't logically belong in the class. If you build them in, they can
touch the data. That means if you want to change the data layout, you have to
review 60 functions, and make changes in 60 places.
Instead, if you build a relatively simple interface to a
Date class, you might have five or
ten member functions that are there because they are logically necessary, or for
performance reasons. It's hard for me to imagine a performance reason for a
Date, but in
general that's an important concern. Then you get these five or ten operations, and you
can build the other 50 in a supporting library. That way of thinking is fairly well
accepted these days. Even in Java, you have the containers and then the supporting library
of static methods.
I've been preaching this song for the better part of 20 years. But people got very keen on
putting everything in classes and hierarchies. I've seen the
Date problem solved by having
a base class
Date with some operations on it and the data protected, with utility functions
provided by deriving a new class and adding the utility functions. You get really messy
systems like that, and there's no reason for having the utility functions in derived
classes. You want the utility functions to the side so you can combine them freely. How
else do I get your utility functions and my utility functions also? The utility functions
you wrote are independent from the ones I wrote, and so they should be independent in the
code. If I derive from class
Date, and you derive from class
Date, a third person won't be able to easily use both of our utility functions,
because we have built dependencies in that didn't need to be there. So you can
overdo this class hierarchy stuff.
Come back Monday, October 20 for the next installment of this conversation with Bjarne Stroustrup. If you'd like to receive a brief weekly email announcing new articles at Artima.com, please subscribe to the Artima Newsletter.
Bjarne Stroustrup is author of The C++ Programming Language, which
is available on Amazon.com at:
Bjarne Stroustrup is author of The Design and Evolution of C++, which
is available on Amazon.com at:
Bjarne Stroustrup's home page:
Bjarne Stroustrup's page about the C++ Programming Language:
Publications by Bjarne Stroustrup:
Interviews with Bjarne Stroustrup:
Bjarne Stroustrup's FAQ:
Bjarne Stroustrup's C++ Style and Technique FAQ:
Bjarne Stroustrup's C++ Glossary:
Libsigc++ Callback Framework for C++:
C++ Boost, peer-reviewed portable C++ source libraries: