Object-oriented techniques using classes and virtual functions are an
important way to develop large, complex software applications and
systems. So are generic programming techniques using templates.
Both are important ways to express polymorphism – at run time and at
compile time, respectively. And they work great together in C++.
There are lots of definitions of “object oriented”, “object-oriented
programming”, and “object-oriented programming languages”. For a longish
explanation of what Stroustrup thinks of as “object oriented”, read
Why C++ isn’t just an object-oriented programming language.
That said, object-oriented programming is a style of programming
originating with Simula (about 40 years ago!) relying on encapsulation,
inheritance, and polymorphism. In the context of C++ (and of many other
languages with their roots in Simula), it means programming using class
hierarchies and virtual functions to allow manipulation of objects of a
variety of types through well-defined interfaces and to allow a program
to be extended incrementally through derivation.
See whats so great about classes
for an idea about what’s great about “plain classes”. The point about
arranging classes into a class hierarchy is to express hierarchical
relationships among classes and to use those relationships to simplify
code.
To really understand OOP, look for some examples. For example, you
might have two (or more) device drivers with a common interface:
This Driver
is simply an interface. It is defined with no data members and a set of pure virtual functions. A Driver
can be used through this interface and many different kinds of drivers can implement this interface:
Note that these drivers hold data (state) and objects of them can be created. They implement the functions defined in Driver
. We can imagine a driver being used like this:
The key point here is that f()
doesn’t need to know which kind of driver it uses; all it needs to know is that it is passed a Driver
; that is, an interface to many different kinds of drivers. We could invoke f()
like this:
Note that when f()
uses a Driver
the right kind of operations are implicitly chosen at run time. For example, when f()
is passed d1
, d.read()
uses Driver1::read()
, whereas when f()
is passed d2
, d.read()
uses Driver2::read()
. This is sometimes called run-time dispatch or dynamic dispatch. In this case there is no way that f()
could know the kind of device it is called with because we choose it based on an input.
Please note that object-oriented programming is not a panacea. “OOP”
does not simply mean “good” – if there are no inherent hierarchical
relationships among the fundamental concepts in your problem then no
amount of hierarchy and virtual functions will improve your code. The
strength of OOP is that there are many problems that can be usefully
expressed using class hierarchies – the main weakness of OOP is that too
many people try to force too many problems into a hierarchical mold.
Not every program should be object-oriented. As alternatives, consider plain classes, generic programming, and free-standing functions (as in math, C, and Fortran).
If you’re still wondering “why OO?”, consider also business reasons:
The software industry is succeeding at automating many of life’s functions that used to be manual. In addition, software is
improving the flexibility of devices that were previously automated, for example, transforming the internal
implementation of many previously existing devices from mechanical to software (clocks, automobile ignition systems,
etc.) or from being controlled by electrical circuitry to software (TVs, kitchen appliances, etc.). And, of course,
software is integrated into every aspect of our daily business lives — originally software was limited to Accounting
and Finance, but it is now embedded in Operations, Marketing, Sales, and Management — software is nearly everywhere.
This incredible success has constantly stressed the ability of the software development organizations to keep up. As an
industry, software development has continuously failed to meet the demands for large, complex software systems. Yes,
this failure is actually due to the success of software’s ability to bring perceived value — it is actually caused
because demand is greater than our ability to satisfy that demand. And while it is possible for us software people to
sit around and pat ourselves on the back for that demand, innovators and thought leaders in this and every other
discipline are marked by one undeniable characteristic: they/we are not satisfied. As an industry, we must do better. A
lot better. Uber better.
Our past successes have propelled users to ask for more. We created a market hunger that Structured Analysis, Design and
Programming techniques have not been able to satisfy. This required us to create a better paradigm. Several, in fact.
C++ supports OO programming. C++ can also be used as a traditional, imperative programming language (“as a better
C”) or using the generic programming approach. Naturally each of these approaches has its pros and
cons; don’t expect the benefits of one technique while using another. (Most common case of misunderstanding: don’t
expect to get the benefits of object-oriented programming if you’re using C++ as a better C.)
C++ also supports the generic programming approach. And most recently C++ is starting to support
(as opposed to merely allow) the functional programming approach. The best programmers are able to decide which approach
fits best in which situation, rather than trying to shove a single approach (“my favorite approach”) at every problem
everywhere in every industry irrespective of the business context or the sponsor’s goals.
Most importantly, sometimes the best solution is achieved by using a
combination of features from Object-Oriented, Generic and Functional
programming styles, whereas trying to restrict oneself to one particular
approach may lead to a suboptimal solution.