- Overview
- Table of Contents
- Special Member Functions: Constructors, Destructors, and the Assignment Operator
- Operator Overloading
- Memory Management
- Templates
- Namespaces
- Time and Date Library
- Streams
- Object-Oriented Programming and Design Principles
- The Standard Template Library (STL) and Generic Programming
- Exception Handling
- Runtime Type Information (RTTI)
- Signal Processing
- Creating Persistent Objects
- Bit Fields
- New Cast Operators
- Environment Variables
- Variadic Functions
- Pointers to Functions
- Function Objects
- Pointers to Members
- Lock Files
- Design Patterns
- Dynamic Linking
- Tips and Techniques
- A Tour of C99
- C++0X: The New Face of Standard C++
- C++0x Concurrency
- The Reflecting Circle New
- We Have Mail New
- The Soapbox
- Numeric Types and Arithmetic
- Careers
- Locales and Internationalization
The Rise & Fall of Object Orientation
Last updated Sep 10, 2004.
Slowly but surely, state-of-the-art C++ programming is becoming less and less object-oriented. No, C++ isn't falling back to procedural programming -- quite to the contrary. However, if you examine standard C++ libraries (such as STL,
It wouldn't be an exaggeration to say that classic object-orientation is now a thing of the past. Today, I will discuss this paradigm shift and its causes.
Traditional Object-Oriented Concepts
The roots of object orientation date back to the late 1960s, when languages such as Modula-2 implemented dynamic binding, encapsulation, and inheritance. Later programming languages (particularly Smalltalk 72) refined these concepts, forming a new paradigm called "object-oriented programming." If anything, OOP focused on the ability to define autonomous units that bundle data and related operations as an object. Many of the early OO languages relied on dynamic typing: virtual functions, inheritance and the ability to pick and mix properties of multiple objects at runtime.
As always, proponents of the new paradigm promised a higher level of code reuse, resilience to design changes, safety, reliability and portability. Indeed, if you compare antiquated programming languages such as Fortran and COBOL to Smalltalk, OO has fulfilled these promises to some extent. Yet today, more than a decade after the revolution that made OO a predominant programming paradigm, traditional OO programming is a disappointment. Diplomatic understatements and lip-service won't change this fact. Let's see why OO programming has lost its luster in C++.
Code Reuse
Code reuse was OOP's biggest promise. Pundits claimed that by using virtual member functions and inheritance, you could get the ultimate level of code reuse while still benefiting from resilience to design changes. In practice, though, this software design model has resulted in a large number of extremely inefficient frameworks and libraries.
Take MFC for example. Its set of collection classes is light years behind STL. The problem isn't with MFC, though. Similar frameworks, say OWL and National Institute of Health Class Library (NIHCL), repeated exactly the same design mistakes: deriving every collection class from an abstract collection class and relying on virtual member functions and dynamic typing. This suggests that OO itself is the culprit, not the designers.
Member Functions
Member functions (or methods, as they are called in other programming languages) are a fundamental aspect of OOP. Think for minute: What could be more logical and properly designed than a member function that is familiar with the intimate details of its object?
Obvious advantages not withstanding, member functions are disfavored in state-of-the-art C++ libraries. The problem is that a member function is useful only for a single class. This is the bane of code reuse. Think, for example, of a library of containers. If each container class defines its own size() member function, you will end up writing numerous size() functions that basically do the same thing.
The alternative is to define a generic function (let's call it an algorithm to make it more palatable for hard core OO aficionados) that works for every container type. Isn't this a step backwards into the good old days of procedural programming, where data structures and functions that manipulated them were kept apart? Syntactically speaking -- yes. Generic programming looks like procedural programming more than object-oriented programming. Yet under the hood, things work very differently. A generic algorithm, unlike a typical function in a procedural language, knows very little about the data structure it manipulates. Furthermore, it doesn't peek into the data structure directly but uses iterators for this purpose. So the syntactic similarity is misleading.
Should member functions be thrown out of the window? Not quite. They are useful under the following conditions: when the language forces you to use a member function (constructors, destructors and conversion operators for example), or when the member function implements a service that is meaningful only in the context of a certain class. An example of this is a connect() member function of a socket class. In most other cases, experience has shown that common operations such as insert(), assign(), replace(), sort(), and copy() , which are applicable to a very wide set of objects, are best implemented as freestanding algorithms rather than being replicated as a member function in every class.
Inheritance
Another holy cow of traditional OO design is inheritance. STL hardly uses inheritance; it seems that the newer a C++ library is, the less it uses inheritance. Why?
In theory at least, inheritance is the primary means of reuse. However, it incurs a lot of complications and overhead. First, every class hierarchy that relies on inheritance must have a virtual destructor and virtual functions. For certain abstractions, such as C++ streams, this model is used effectively. Yet, in other abstractions such as containers, there's little motivation to derive container classes from a common base class.
Inheritance incurs another onerous problem: it propagates bugs. A faulty member function in a base class contaminates every derived class. Even if the base class' functions are bug-free, derived classes often carry the excess baggage inherited from their base classes. Look at cout, for example. How many programmers ever bother to use the numerous member functions that the ios_base and basic_ios base classes pass on to them?
By forgoing inheritance, STL containers don't carry such excess baggage. Furthermore, the fact that every container class is independent enables implementers to optimize each of them.
Conclusions
One trick that OO diehards use is to broaden its definition. They claim that a through and through OO language should provide facilities for generic programming and functional programming in addition to the traditional OO concepts. Under this broad definition, the recent additions to the Standard Library still follow the OO model. However, this is just a verbal maneuver that is meant to camouflage the real facts: OO in its classic sense is a thing of the past, at least in C++. Experience has shown that generic programming and to a smaller extent, functional programming, achieve a high level of code reuse, portability and efficiency.
Books
Exceptional C++ Style, by Herb Sutter, contains a systematic analysis of class std::string. Sutter shows that the majority of its member functions can actually be replaced with existing STL algorithms, at least in theory. He also provides plenty of reasons why this approach is better. The four mini-chapters dedicated to this issue are indispensable for anyone who is interested in learning how general purpose libraries should be designed today. It's surprising to see that the original notions of OOP such as inheritance, virtual functions and member functions have become so antiquated and cumbersome in the last 10 years. Undoubtedly, were class std::string to be re-designed from scratch today, it would offer a cleaner, slimmer and more consistent interface
.

Account Sign In
View your cart