# Comparison and Inheritance

We continue last week's discussion of comparison functions by thinking about how to compare objects from different parts of an inheritance hierarchy.

##
More Insights

### White Papers

More >>### Reports

More >>### Webcasts

- Tools & Techniques to Disrupt Lateral Movement
- Overcoming Cyberthreats to Critical Infrastructure with Integrated IT/OT Cybersecurity

Suppose we have a `Vehicle`

class from which we have derived `Car`

, `Truck`

, and other classes such as `Airplane`

. How do we go about defining comparisons between objects of these various classes that will meet the C++ ordering requirements in a useful way?

Implementing these comparisons will have its own problems, because C++ objects are not polymorphic by themselves. To take advantage of polymorphism in C++, we must use pointers or references, perhaps directly, or perhaps through some kind of smart pointer class. Nevertheless, it is probably useful to think about the characteristics that such comparison functions might have before we worry about the details of implementing them.

The obvious problem in defining such operations is that there might well be an obvious strategy for comparing one `Car`

with another, or one `Truck`

with another — but it might not be obvious how to extend this strategy to allow comparing a `Car`

with a `Truck`

. For example, every `Car`

might have a serial number, and every `Truck`

might have a serial number, but these two kinds of serial numbers might be in completely different formats.

One's first temptation might be to use the obvious methods of comparing serial numbers between two objects of the same type, and then to say that two objects of different types are unrelated. In other words, define the < operation on two `Vehicle`

objects to return false unless the two objects have the same type. In that case, < should compare the objects' contents (serial numbers, or any other of the objects' aspects we might choose).

This strategy is a disaster. To see why, consider two `Car`

objects `c1`

and `c2`

, and a `Truck`

object `t`

. Under this definition, `c1`

is unrelated to `t`

, `t`

is unrelated to `c2`

, but one of `c1 < c2`

and `c2 < c1`

will be true. This behavior violates the C++ rules for order relations, because the *unrelated* operation is not transitive. In effect, when you're designing a comparison function, you should think of `unrelated`

as meaning `conceptually equivalent`

.

With this insight, let's revisit our original problem: We know how to compare two `Car`

s, we know how to compare two `Truck`

s, and we need to figure out how to compare a `Car`

with a `Truck`

. We can imagine this problem as if we had two stacks of cards, representing `Car`

s and `Truck`

s, respectively, and we want a way to shuffle these stacks of cards together into a single stack, while still preserving the ordering within each of the original stacks.

If the problem is described that way, it should be obvious that the easiest way to solve it is to put one stack on top of the other. Translating this solution into a comparison function is trivial: We decree that an object of one type is always less than an object of the other type, and continue to use the comparisons that we have already defined within each type. Another way to look at this solution is that each object becomes a string of two symbols, where the first one is the object's type, and we define ordering as dictionary ordering over these two-symbol strings.

The more general kind of "shuffling" is harder to define, because we need to be able to compare objects of different types while ensuring that the results of such comparisons are always consistent with each other. However, using dictionary order suggests one way of doing it: We find a string — such as a serial number — that describes each object, and then create a pair in which the *first* component is that string and the *second* component represents the type.

This strategy suggests a general technique: Represent each object's contents with a *canonical value* — a value of a single, well-defined type that already has an order relation defined on it. Then use the canonical values for comparison, perhaps appending the object's type if we want two objects of different types with the same canonical value to be ordered rather than conceptually equivalent.

That's the general strategy. Now it's your turn. Imagine an inheritance hierarchy with `Number`

at its root and `Integer`

and `Real`

each derived from `Number`

. Assume that an `Integer`

is a container for an `int`

, and `Real`

is a container for a `float`

. How would you define a comparison operation between two `Number`

s? How would you implement it? Beware: This problem is nowhere near as simple as it looks.