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:
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
Driver1::read(), whereas when
f() is passed
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.