Translating OO models and designs to C++

by Doug Lea.


Implementing classes in C++ takes an idiomatic form when based on a set of OO models/designs. As with modeling, the concerns can be loosely categorized into four cells:

This document describes only the basic design decisions and techniques for the most common aspects of each cell, and only as they apply to sequential programming in C++. Other forms and strategies are described in Design Patterns, OOSD, and elsewhere.


Even though we'll concentrate on bottom-level implementation matters, ideally, every Analysis/Design-level class should be represented in C++ as an abstract class. The reasons include:

On the other hand, there are a few disadvantages, leading to cases where they are almost never used:

Additionally, it is equally common (or perhaps even more common) to first establish particular concrete classes, and then abstract away details to arrive at more abstract classes.


There are two sides of how to go about implementing values described as attributes in models, access and update.

Accessing attributes

I'll illustrate with the example of a Thermostat with currentTemperature and desiredTemperature attributes. There is a continuum of possibilities for implementation. Some of them don't make a lot of sense in this example, but are listed for completeness.
Direct storage via primitive types.
For example, declaring a field float curTemp.
Direct storage via ADT classes.
For example, defining an ADT class Temperature and a field in Thermostat listed as Temperature curTemp.
Indirect storage.
For example, declaring a field float* curTemp, and in the Thermostat constructor, attaching it to a new'd float.
Static storage
For example, if all instances should maintain the same desiredTemperature, declaring a static float desTemp_.
Shared indirect storage
For example, if all instances in the same house maintain the same desiredTemperature, declaring a float desTemp_ in a House class, and linking all Thermostat values to it when they are installed in the House
Data base queries.
For example, if a data base maintains desired temperatures, implementing desiredTemperature as a database query via some kind of database interface.
Subclassing a class defining storage.
For example, declaring that Thermostat : public Temperature.
Constant computation.
For example, if desiredTemperature were always 68, defining float desiredTemperature() { return 68; }, or nearly equivalently but more flexibly, defining a static const float theDesTemp = 68 and returning its value.
Local computation.
For example, if desiredTemperature were always 5 degrees greater than the current temperature, defining float desiredTemperature() { return currentTemperature + 5; }
Delegated computation.
For example, computing currentTemperature by asking a TemperatureSensor known via a connection attribute. Note that delegated computation can sometimes fail (for example if the link is null), so exception mechanics may be needed.
For example, using any computation-based scheme, but also maintaining a stored representation of the previous result, along with some way of known whether it is still valid.
Subclassing a class defining computation
For example, overriding one of the above versions.

Updating attributes

The following strategies interact with access mechanics; only a few combinations of them make sense.
Direct updates.
For example, defining raiseDesTemp(float delta) as { desTemp_ += delta; }.
Indirect updates.
For example, offloading the update mechanics to the object maintaining the actual representation.
History-preserving updates.
For example, saving the history of raiseDesTemp in a list so previous states can be reverted to.
For example, with an indirect scheme, rebinding to a new float on update rather than modifying the current one. Similarly, but with additional mechanics for reference-counting schemes.
Constrained updates.
For example, if desired temperatures must always lie in some range, ensuring that these constraints hold after the update by clipping them to bounds.
Exceptionable updates.
For example, if desired temperatures must always lie in some range, raising an exception if a client tried to set to an impossible value. Normally, these should operate transaction style: If the attempted update fails, the state is guaranteed to be that holding before the attempted update.
Propagated updates.
For example, notifying a Heater if the desiredTemperature is changed.
Global restructuring on update.
For example, if an attempt to set the desired temperature in one room of a house requires recomputing a global optimal collection of settings through the house, invoking a general-purpose constraint solver that does this, perhaps even resulting in the actual setting for the current thermostat to be different than requested.
Database updates.
For example, issuing a database transaction every time that desiredTemperature is changed.
Indirect propagation.
For example, defining a PropagatableTemperature class that accepts a list of objects to notify when it is changed, and delegating all accesses and updates to it.


Last update Wed May 10 06:05:25 1995 Doug Lea (dl at gee)