How Polymorphism Enables Flexible Code | C++ Programming

UPI Study › Courses › C++ Programming › Free lessons › Lesson 11.5 Free with a free account

How Polymorphism Enables Flexible Code

C++ Programming · Chapter 11: Inheritance and PolymorphismSection 11.5 · lesson 82 of 84video coming soon

← Why Encapsulation Strengthens DesignNext lesson: Hand-Crafting Linked Lists →

Earn 3 college credits for C++ ProgrammingRecommended for credit by NCCRS · Computer Science 242 · self-paced · one payment, lifetime access

In this lesson

One call, different responses.

  • Make a function virtual, and say what decides which runs
  • Sketch how a virtual call is dispatched, and its cost
  • Use pure virtual functions, abstract types and virtual destructors
  • Avoid slicing; operate on a mixed collection with one loop

Transcript

Everything said in the video, section by section (1,649 words). Read along, or search it.

Opening Problem

A Mixed Collection of Shapes

Picture a drawing program with circles, squares and triangles, all in one list. You loop over it, and call draw on each one. You'd want each shape to draw itself. Inheritance so far has let a derived type reuse and extend a base. What it hasn't done is let a program hold a mixed collection, and treat every item the same way, while each behaves as itself. This section closes that gap, with one keyword, and one requirement about how objects are held. There are also a few consequences that must be learned together, because each one bites when the others are forgotten.

The Gap

Base Pointer Runs the Base Draw

Derived types can already replace a base function. What they can't yet do is have that replacement chosen when the object is handled as a base. And being handled as a base is exactly what makes a mixed collection possible. Here, circle redefines draw. A shape pointer holds the address of a circle, and draw is called through it. It runs shape's draw, which isn't what was wanted. The object really is a circle. But the decision was made when the program was built, using the only thing available then: the pointer's type.

Comparison

Deciding the Call Two Ways

Here's the same call, decided two ways. Without virtual, the call is decided when the program is built, from the pointer. The circle object is never consulted, and shape's draw runs. With virtual, the call is decided while the program runs, from the object itself. The object is asked what it is, and circle's draw runs. Adding one keyword moves the decision from the pointer to the object.

Core Syntax

Virtual, Override, and Three Rules

Here's that keyword. The base declares draw as virtual, meaning it may be replaced by derived types. It also declares a virtual destructor, which isn't optional, as we'll see shortly. Circle's draw is marked override, and replaces the base version. Now the call through the shape pointer runs circle's draw. Three rules govern this. The base declares the function virtual. The derived version must have the same signature: same name, same parameters, same constness. Otherwise it becomes a different function, not a replacement. And once a function is virtual, it stays virtual in every type below, whether or not the word is repeated.

Best Practice

Let the Compiler Check Signatures

Write override. It adds no behavior, but a mistyped name or missing const becomes an error at that line. Without it, a slightly different signature still builds, at most with a warning, and the wrong function runs forever. With it, the compiler checks you, and points at the mismatch: const against no const.

Summary Rule

Pointer Type Versus Object Type

So here's what decides which function runs. A non-virtual call is decided when the program is built, from the type of the pointer or reference. A virtual call is decided as the program runs, from the type of the object. So through a base pointer to a derived object, the non-virtual call runs the base version, and the virtual call runs the derived version. Adding a new derived type means editing every place that switches on type. With virtual calls, nothing calling through the base changes. The cost of a virtual call is one extra step per call, and one hidden pointer per object.

Requirement

Pointers and References Enable Dispatch

Dynamic dispatch needs an object whose real type may differ, from the type of the name used to reach it. A pointer and a reference both allow that. Through either one, the circle's draw runs. A plain variable doesn't, because a variable of type shape is a shape, and nothing else. Assigning a circle into one does something quite different, which we'll come to.

Under the Hood

Hidden Tables of Function Addresses

Here's how the choice is usually made, underneath. A type with virtual functions gets a hidden table of function addresses, one per type, with one entry per virtual function. Each object carries a hidden pointer to its own type's table. A virtual call reads that pointer, looks up the entry, and jumps there. So an object built as a circle carries the circle's table, and the call lands in the circle's code.

Cost

Seeing the Hidden Pointer in Size

The cost is one hidden pointer per object, and one extra read per call. You can see the pointer in the object's size. Two structs, identical except that one has a virtual function. On this 64-bit machine, the plain one takes four bytes, and the polymorphic one takes sixteen: the hidden pointer, plus padding. Both costs are small, and both are real, which is why functions aren't virtual by default.

Pure Virtual

Equals Zero Makes Shape Abstract

Sometimes a base has no sensible implementation to offer. There's no default way to draw a shape. Only circles and squares can be drawn. Writing equals zero declares the function pure virtual. Every derived type must supply it, and the base itself can no longer be instantiated. Declaring a plain shape is refused, because shape is abstract. But a shape pointer to a circle is fine. Pointers to it are the point.

Type Categories

Concrete, Abstract, and Interface Types

That gives three kinds of type. A concrete type implements every function, and can be instantiated, so it's used as objects, directly. An abstract type has at least one pure virtual function, and maybe some implemented ones and data. It's used only as a base, a partial implementation that derived types complete. An interface-style type has nothing but pure virtual functions and a virtual destructor. It's a pure statement of what an implementer must provide. An abstract base is a promise the compiler enforces. Any type that fails to supply a pure virtual function is itself abstract. So the obligation can't be forgotten, only passed further down.

Common Mistake

Non-Virtual Destructors Lose Resources

If objects are deleted through base pointers, and with polymorphism they usually are, the destructor obeys the same rule as every other function. A non-virtual destructor is chosen by the pointer's type. Only the shape destructor runs. Whatever the circle owned, whether memory, an open file, or a connection, is silently lost. And nothing reports it. The language calls this undefined behavior, so this program is never run. The compiler can warn, though, and here is its real warning.

The Fix

Virtual Destructors Destroy Whole Objects

With a virtual destructor, the circle's destructor runs, then the shape's. The object is destroyed as what it actually is. So the rule is: if a type has any virtual function, give it a virtual destructor. It costs nothing, because the type already carries the hidden pointer.

Common Mistake

Slicing Discards the Derived Part

Assigning a derived object into a base variable doesn't fail, and doesn't warn. It copies the base part, and discards everything else, including the object's identity as a derived type. So the sliced copy runs shape's draw. It's genuinely a shape now. The second case catches people. A function taking a shape by value slices every argument on the way in, and nothing at the call looks unusual. The remedy is one character: take a const shape reference, and the object arrives whole. Make it a habit. Polymorphic objects are passed and held by reference or pointer, never by value.

Contrast

Overloading Versus Virtual Dispatch

Overloading already gave one name several meanings. Virtual dispatch gives one call several behaviors. Both are called polymorphism, and they resolve at opposite ends of a program's life. Here, describe is overloaded, and the compiler picks the version from the argument's type. But a-arrow-draw and b-arrow-draw are the same call, and each runs its own object's version.

Comparison

Build Time Versus Run Time Resolution

Overloading is resolved when the program is built, chosen by the number and types of the arguments, with no run-time cost. Virtual calls are resolved each time the call is reached, chosen by the object's actual type, for one table lookup per call. And to extend them, overloading needs a new overload and a rebuild of every caller. Virtual dispatch needs a new derived type, and existing calling code is untouched.

Payoff

Adding a Hexagon Without Editing

Everything so far exists for this. A vector of unique pointers to shape can hold a circle, a square, and a triangle. One loop draws each shape, and adds up their areas, without knowing or asking what any of them is. Now add a hexagon. It derives from shape, supplies draw and area, and is pushed into the same container. The loop doesn't change, not by one character, because it never mentioned any particular shape. Run it again, and the hexagon draws itself and adds its area. That's the whole return on the machinery. New behavior is added by writing a new type, not by editing existing code.

Design Choice

Why Unique Pointers Hold Shapes

Why unique pointer, rather than plain pointers? A container of raw shape pointers works, and leaves you owing a delete for every element, on every path out. Holding unique pointers instead means each object is released when the container is, automatically. And the virtual destructor is what makes that correct. A unique pointer deletes through a shape pointer, which is exactly the case a non-virtual destructor gets wrong.

Key Terms

Polymorphism Vocabulary Review

Key terms. Polymorphism is one call producing different behavior, according to what the object actually is. Dynamic binding chooses the function at the moment of the call, from the object's real type. Static binding chooses when the program is built, from the type of the name used. A pure virtual function is declared equals zero, with no base version. A type with at least one is abstract, and objects of it can't be created. Object slicing is copying a derived object into a base variable, which discards the derived part. And the dispatch table is the per-type table of function addresses that a virtual call consults.

Quick Check

Which Draw Runs Without Const?

Quick check. The base draw is virtual and const. Circle declares draw without const, and without override. Called through the shape pointer, which draw runs? Pause and predict. Shape's draw. Without const, the signatures differ, so circle's draw is a separate function, not a replacement. Writing override would have caught it at build time.

Key Takeaways

Five Rules for Polymorphic Code

To summarize. Virtual moves the decision from the pointer to the object. Match signatures exactly, and write override so the compiler checks. Pure virtual functions make abstract bases, enforced by the compiler. Any virtual function means a virtual destructor. And hold polymorphic objects by pointer or reference, never by value.

About C++ Programming at UPI Study

C++ Programming (Computer Science 242) is a self-paced online course of 84 video lessons in 11 chapters, from your first program to decisions, loops, arrays, functions, classes, files, pointers and inheritance. It carries a NCCRS credit recommendation for 3 semester credits.

Every lesson transcript is free on pages like this one, and the videos are free with a free UPI Study account. Enrolling adds a practice quiz after each lesson, the graded work and final exam that earn the credit, lifetime access, and an official transcript on completion. UPI Study has helped over 130,000 students with college credits. ACE and NCCRS have over 2,100 cooperating colleges that will consider accepting credits; the final decision is with the universities.

Why take it here

Why students take C++ Programming with UPI Study

Your first real programming languageC++ teaches how programs really work: variables, control flow, functions, memory and objects. It is the base for data structures, algorithms, systems and game programming, and for most computer-science degrees.
Credit at a fraction of the priceThe same 3 credits can cost $750 or more at a US college. Here it is one payment, self-paced, with lifetime access.
Finish your degree soonerTransfer credits reduce the courses you still owe, so you can graduate earlier or lighten a heavy semester.
Strengthen an applicationA completed college-level course on an official transcript shows admissions and scholarship committees you can do the work.
Study on your own scheduleNo start dates, no deadlines. Watch the lessons, practice, and take the final when you are ready.
130,000+students helped with college creditsACE and NCCRS have over 2,100 cooperating colleges that will consider accepting credits; the final decision is with the universities.

Where the credit goes

Universities that work with NCCRS

C++ Programming carries a NCCRS credit recommendation. These universities work with NCCRS; some are UPI Study's signed partners. ACE and NCCRS have over 2,100 cooperating colleges that will consider accepting credits; the final decision is with the universities.

Find my college → Not listed? Ask Athena in the corner of this page, or book a 15-minute call.

FAQ

Questions students ask us

About How Polymorphism Enables Flexible Code

What does the virtual keyword do in C++, and what decides which function runs?
Virtual makes a function call get decided while the program runs, from the object's actual type. Without virtual, the call is decided at build time from the type of the pointer or reference, so a shape pointer to a circle runs shape's draw. With virtual, circle's draw runs. This dispatch needs a pointer or reference.
Why should I write override on a derived class function in C++?
Writing override makes the compiler check that your function really replaces a virtual base function. It adds no behavior, but a mistyped name or a missing const becomes a build error at that line. Without it, a slightly different signature just creates a separate function, and the base version runs through a base pointer.
Why does a C++ base class need a virtual destructor?
A base class with virtual functions needs a virtual destructor so that deleting through a base pointer destroys the whole object. With a non-virtual destructor, the pointer's type picks the destructor, so only the base one runs and whatever the derived part owned is lost. This is undefined behavior. Unique pointers to a base rely on this rule.
What is object slicing in C++, and how do I avoid it?
Slicing happens when a derived object is copied into a base variable, which keeps only the base part and discards the rest, including its derived identity. Passing a shape by value slices every argument too. Avoid it by taking a const shape reference or pointer instead. Then the object arrives whole and virtual calls run the derived version.

About the C++ Programming course

How many college credits is C++ Programming?
C++ Programming (Computer Science 242) carries a NCCRS credit recommendation for 3 semester credits. ACE and NCCRS have over 2,100 cooperating colleges that will consider accepting credits; the final decision is with the universities.
How much does C++ Programming cost and what is included?
$250, paid once, with lifetime access: all 84 video lessons, a practice quiz after each, the graded work and final exam, and an official transcript when you finish. Students who want several courses choose UPI Study PRO instead.
Which universities accept UPI Study credits?
UPI Study has signed partner agreements with universities such as Thomas Edison State University, University of the People and Charter Oak State College, and works with universities such as Penn State, SNHU and SUNY. ACE and NCCRS have over 2,100 cooperating colleges that will consider accepting credits; the final decision is with the universities. Use "Find my college" on this page to check yours.
How long does C++ Programming take?
It is self-paced with no deadlines. The 84 lessons total about 0 hours of video; most students add reading and practice time and finish in a few weeks to a few months, on their own schedule.
How are the exams done?
Quizzes and assignments are online in the course. The final exam is taken online with proctoring; a person on the UPI Study team checks assignments, usually within two days.
How do my credits reach my college?
When the course shows 100% complete, request the official transcript from your dashboard. It is sent to the college you name, and you can send it to more than one. Check your college first with "Find my college" on this page, or ask Athena.

Ready for the full course? Earn 3 credits in C++ Programming.

84 video lessons, a quiz after each one, and the graded work that earns the credit. Recommended for credit by NCCRS.

C++ Programming
3 credits · NCCRS
Enroll →