Home > Articles

The Well-Crafted Class in C++: Part 2

  • Print
  • + Share This
The well-crafted C++ library should be full-featured and complete. In this last article of his series, Hal Fulton emphasizes that the developer should also pay special attention to areas such as testing and documentation, both of which are neglected too often.
Like this article? We recommend

In the first article of this series, I talked about some issues surrounding the careful creation of a quality class or library in C++. You looked at constructors, methods and parameter lists, and issues with inheritance. Most of these issues are just good OOP practices mixed with common sense.

You may have noticed that I didn't always separate interface issues from implementation issues. I'm not stressing that separation here, either.

That distinction exists for the library user, to be sure. When I call a method that happens to access an internal data structure, I should not generally care whether that structure is an array or a B-tree. To keep a stable application programming interface (API), we make this separation between interface and implementation. Besides assuring future compatibility, it also prevents our having to change documentation in most cases.

But the programmer who is designing the library doesn't have the luxury of dealing only with the interface. Obviously, the interface has to have an implementation behind it. With that in mind, let's proceed.

Effective Operator Overloading

C++ makes operator overloading possible. Where appropriate, go ahead and use this feature. But when you do so, make sure that you choose the operators in a sensible way, and make sure that there are no "holes" in the set of operators. If it makes sense to define a +, it probably makes sense to define a also (although sometimes it may not). If you define + and -, you probably want to define += and -= also.

Logically, you should avoid repeating code. Define the more complex functions in terms of the simpler ones; for example, let += call the + operator. On the other hand, if you want to avoid the creation of a new object when += is invoked, violate this rule and duplicate the code.

Here's a contrived example:

    class Dollar
     int _amt, _cents;


     Dollar(amt,cents=0) : _amt(amt), _cents(cents) {}

     Dollar operator+(Dollar other) 
      int val = _amt + other._amt;
      int cval = _cents + other._cents;
      return Dollar(val,cval);

     Dollar operator+=(Dollar other)
      _amt += other._amt;
      _cents += other._cents;
      return *this;      // Avoid creating new object

Of course, in such a simple case, code duplication is not a real issue, and here we haven't actually duplicated anything word for word, anyhow. In other cases, the common code might be several lines, in which case you might wish to put it in a common private method, to be called more than once.

If you define ++ and -- operators, make sure that you define both forms (pre- and post-). Make sure also that they behave in the traditional way we expect. The prefix form does the increment before the value is returned; the postfix form saves the value to be returned and then does the increment. There's nothing in C++ to enforce this—you have to follow the convention consciously.

Here we continue the same example:

    class Dollar
     // ...

     Dollar operator++()     // prefix
      return *this;

     Dollar operator++(int)    // postfix
      int val = _amt++;
      Dollar obj = Dollar(val); // Or the one-liner:
      return obj;        // return Dollar(_amt++);

There might be some operators (such as () and ->) that you rarely want to define. If you simply don't want them to be used, you can always just ignore them. Alternatively, you could raise an exception that explicitly points out that these are not implemented.

If inheritance is an issue, there is a third and better alternative. You could make these virtual functions so that child classes could define them as needed.

  • + Share This
  • 🔖 Save To Your Account