What makes this problem more unsettling is that both you and I tried to do everything by the book. We wrote a safe, typechecked code. We eschewed casts. g++ (2.95.2) compiler with flags -W and -Wall issued not a single warning. […] And yet, despite all my efforts to separate interface and implementation, I failed. Should a programming language or the methodology take at least a part of the blame?
A lot of people miss the point with Object Oriented Programming (OOP) – this author gets it.
Painfully few programmers are aware of the Liskov Substitution Principle (LSP), and its violations incur the wrath of some distant cousin of the the side effect monster, the violated invariant monster. LSP is incredibly tricky to apply – generally, the object metaphor actually gets in the way of verifying LSP properties. The general guidance of OO spends far too little time on these implications. Inevitably, a programmer does something totally reasonable but everything breaks anyway.
I try to follow the LSP when designing green-field. This rarely permits me to use inheritance of implementation. Rather, composition dominates my designs.
For the curious, the author’s program strengthened a precondition – the derived class CSet required the element be unique for insert to ‘succeed’, where CBag required nothing. As such, LSP predicts that CSets cannot be transparently substituted where CBags are predicted; if they are, different behavior may (and did) result.