If you think about the Greek roots of the term, it should become obvious.
- Poly = many: polygon = many-sided, polystyrene = many styrenes (a), polyglot = many languages, and so on.
- Morph = change or form: morphology = study of biological form, Morpheus = the Greek god of dreams able to take any form.
So polymorphism is the ability (in programming) to present the same interface for differing underlying forms (data types).
For example, in many languages, integers and floats are implicitly polymorphic since you can add, subtract, multiply and so on, irrespective of the fact that the types are different. They're rarely considered as objects in the usual term.
But, in that same way, a class like BigDecimal
or Rational
or Imaginary
can also provide those operations, even though they operate on different data types.
The classic example is the Shape
class and all the classes that can inherit from it (square, circle, dodecahedron, irregular polygon, splat and so on).
With polymorphism, each of these classes will have different underlying data. A point shape needs only two co-ordinates (assuming it's in a two-dimensional space of course). A circle needs a center and radius. A square or rectangle needs two co-ordinates for the top left and bottom right corners and (possibly) a rotation. An irregular polygon needs a series of lines.
By making the class responsible for its code as well as its data, you can achieve polymorphism. In this example, every class would have its own Draw()
function and the client code could simply do:
shape.Draw()
to get the correct behavior for any shape.
This is in contrast to the old way of doing things in which the code was separate from the data, and you would have had functions such as drawSquare()
and drawCircle()
.
Object orientation, polymorphism and inheritance are all closely-related concepts and they're vital to know. There have been many "silver bullets" during my long career which basically just fizzled out but the OO paradigm has turned out to be a good one. Learn it, understand it, love it - you'll be glad you did :-)
(a) I originally wrote that as a joke but it turned out to be correct and, therefore, not that funny. The monomer styrene happens to be made from carbon and hydrogen, C8H8
, and polystyrene is made from groups of that, (C8H8)n
.
Perhaps I should have stated that a polyp was many occurrences of the letter p
although, now that I've had to explain the joke, even that doesn't seem funny either.
Sometimes, you should just quit while you're behind :-)
Static polymorphism produces faster code, mostly because of the possibility of aggressive inlining. Virtual functions can rarely be inlined, and mostly in a "non-polymorphic" scenarios. See this item in C++ FAQ. If speed is your goal, you basically have no choice.
On the other hand, not only compile times, but also the readability and debuggability of the code is much worse when using static polymorphism. For instance: abstract methods are a clean way of enforcing implementation of certain interface methods. To achieve the same goal using static polymorphism, you need to restore to concept checking or the curiously recurring template pattern.
The only situation when you really have to use dynamic polymorphism is when the implementation is not available at compile time; for instance, when it's loaded from a dynamic library. In practice though, you may want to exchange performance for cleaner code and faster compilation.
Best Answer
Understanding of / requirements for polymorphism
To understand polymorphism - as the term is used in Computing Science - it helps to start from a simple test for and definition of it. Consider:
Here,
f()
is to perform some operation and is being given valuesx
andy
as inputs.C++ mechanisms for polymorphism
Explicit programmer-specified polymorphism
You can write
f()
such that it can operate on multiple types in any of the following ways:Preprocessing:
Overloading:
Templates:
Virtual dispatch:
Other related mechanisms
Compiler-provided polymorphism for builtin types, Standard conversions, and casting/coercion are discussed later for completeness as:
Terminology
Further categorisation
Given the polymorphic mechanisms above, we can categorise them in various ways:
When is the polymorphic type-specific code selected?
f
above withint
arguments - depending on the polymorphic mechanism used and inlining choices the compiler might avoid generating any code forf(double)
, or generated code might be thrown away at some point in compilation or linking. (all mechanisms above except virtual dispatch)Which types are supported?
Parametric meaning you can just try to use the function for various parameter types without specifically doing anything to enable its support for them (e.g. templates, macros). An object with functions/operators that act like the template/macro expects1 is all that template/macro needs to do its job, with the exact type being irrelevant. The "concepts" introduced by C++20 express and enforce such expectations - see cppreference page here.
Parametric polymorphism provides duck typing - a concept attributed to James Whitcomb Riley who apparently said "When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.".
Subtype (aka inclusion) polymorphism allows you to work on new types without updating the algorithm/function, but they must be derived from the same base class (virtual dispatch)
1 - Templates are extremely flexible. SFINAE (see also
std::enable_if
) effectively allows several sets of expectations for parametric polymorphism. For example, you might encode that when the type of data you're processing has a.size()
member you'll use one function, otherwise another function that doesn't need.size()
(but presumably suffers in some way - e.g. using the slowerstrlen()
or not printing as useful a message in the log). You can also specify ad-hoc behaviours when the template is instantiated with specific parameters, either leaving some parameters parametric (partial template specialisation) or not (full specialisation)."Polymorphic"
Alf Steinbach comments that in the C++ Standard polymorphic only refers to run-time polymorphism using virtual dispatch. General Comp. Sci. meaning is more inclusive, as per C++ creator Bjarne Stroustrup's glossary (http://www.stroustrup.com/glossary.html):
This answer - like the question - relates C++ features to the Comp. Sci. terminology.
Discussion
With the C++ Standard using a narrower definition of "polymorphism" than the Comp. Sci. community, to ensure mutual understanding for your audience consider...
Still, what's crucial to being a great C++ programmer is understanding what polymorphism's really doing for you...
letting you write "algorithmic" code once and then apply it to many types of data
...and then be very aware of how different polymorphic mechanisms match your actual needs.
Run-time polymorphism suits:
Base*
s,When there's not a clear driver for run-time polymorphism, compile-time options are often preferable. Consider:
__FILE__
,__LINE__
, string literal concatenation and other unique capabilities of macros (which remain evil ;-))Other mechanisms supporting polymorphism
As promised, for completeness several peripheral topics are covered:
This answer concludes with a discussion of how the above combine to empower and simplify polymorphic code - especially parametric polymorphism (templates and macros).
Mechanisms for mapping to type-specific operations
> Implicit compiler-provided overloads
Conceptually, the compiler overloads many operators for builtin types. It's not conceptually different from user-specified overloading, but is listed as it's easily overlooked. For example, you can add to
int
s anddouble
s using the same notationx += 2
and the compiler produces:Overloading then seamlessly extends to user-defined types:
Compiler-provided overloads for basic types is common in high-level (3GL+) computer languages, and explicit discussion of polymorphism generally implies something more. (2GLs - assembly languages - often require the programmer to explicitly use different mnemonics for different types.)
> Standard conversions
The C++ Standard's fourth section describes Standard conversions.
The first point summarises nicely (from an old draft - hopefully still substantially correct):
Zero or one conversion from the following set: lvalue-to-rvalue conversion, array-to-pointer conversion, and function-to-pointer conversion.
Zero or one conversion from the following set: integral promotions, floating point promotion, integral conversions, floating point conversions, floating-integral conversions, pointer conversions, pointer to member conversions, and boolean conversions.
Zero or one qualification conversion.
These conversions allow code such as:
Applying the earlier test:
a()
itself runs code specifically fordouble
and is therefore not polymorphic.But, in the second call to
a()
the compiler knows to generate type-appropriate code for a "floating point promotion" (Standard §4) to convert42
to42.0
. That extra code is in the calling function. We'll discuss the significance of this in the conclusion.> Coercion, casts, implicit constructors
These mechanisms allow user-defined classes to specify behaviours akin to builtin types' Standard conversions. Let's have a look:
Here, the object
std::cin
is evaluated in a boolean context, with the help of a conversion operator. This can be conceptually grouped with "integral promotions" et al from the Standard conversions in the topic above.Implicit constructors effectively do the same thing, but are controlled by the cast-to type:
Implications of compiler-provided overloads, conversions and coercion
Consider:
If we want the amount
x
to be treated as a real number during the division (i.e. be 6.5 rather than rounded down to 6), we only need change totypedef double Amount
.That's nice, but it wouldn't have been too much work to make the code explicitly "type correct":
But, consider that we can transform the first version into a
template
:It's due to those little "convenience features" that it can be so easily instantiated for either
int
ordouble
and work as intended. Without these features, we'd need explicit casts, type traits and/or policy classes, some verbose, error-prone mess like:So, compiler-provided operator overloading for builtin types, Standard conversions, casting / coercion / implicit constructors - they all contribute subtle support for polymorphism. From the definition at the top of this answer, they address "finding and executing type-appropriate code" by mapping:
"away" from parameter types
from the many data types polymorphic algorithmic code handles
to code written for a (potentially lesser) number of (the same or other) types.
"to" parametric types from values of constant type
They do not establish polymorphic contexts by themselves, but do help empower/simplify code inside such contexts.
You may feel cheated... it doesn't seem like much. The significance is that in parametric polymorphic contexts (i.e. inside templates or macros), we're trying to support an arbitrarily large range of types but often want to express operations on them in terms of other functions, literals and operations that were designed for a small set of types. It reduces the need to create near-identical functions or data on a per-type basis when the operation/value is logically the same. These features cooperate to add an attitude of "best effort", doing what's intuitively expected by using the limited available functions and data and only stopping with an error when there's real ambiguity.
This helps limit the need for polymorphic code supporting polymorphic code, drawing a tighter net around the use of polymorphism so localised use doesn't force widespread use, and making the benefits of polymorphism available as needed without imposing the costs of having to expose implementation at compile time, have multiple copies of the same logical function in the object code to support the used types, and in doing virtual dispatch as opposed to inlining or at least compile-time resolved calls. As is typical in C++, the programmer is given a lot of freedom to control the boundaries within which polymorphism is used.