Overriding Inherited Behavior, Explained with Code Examples

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

Overriding Inherited Behavior

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

← Public, Private, and ProtectedNext lesson: Why Encapsulation Strengthens Design →

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

In this lesson

Rewriting what a derived class inherits.

  • Redefine an inherited function; predict which version runs
  • Explain name hiding, and bring hidden versions back
  • Extend a base version; use override to check replacements
  • Describe static binding, and the problem it creates

Transcript

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

Opening Problem

When Inherited Behavior Is Wrong

A derived type inherits everything the base offers, and some of it will be wrong for the specialized case. Declaring a function with the same name in the derived type replaces it, but only in ways worth knowing exactly. The replacement is decided when the program is built, from the type you wrote, rather than the object you have. That produces a result most people find surprising, and it's the problem section 11.5 exists to solve.

Redefinition

Savings Redefines Describe and Fee

A derived type may declare a member function with the same name as one it inherited. Here, account describes itself and charges a fee of fifty. Savings redefines both, describing itself as a savings account, with a fee of twenty. Calls on a savings object now find the derived version. S dot describe prints savings account. And the base version is still there. Writing account, colon colon, describe, on the same object, prints account. Run it.

Name Lookup

The Search Stops at the Name

Here's how the compiler resolves the name. For s dot describe, it looks in savings first. It finds describe there, and the search stops. Account is only searched if the name wasn't found above. Writing account, colon colon, goes straight to the base version, bypassing the search. Stops as soon as it finds the name is the whole rule. And it's the cause of what comes next.

Name Hiding

One Derived Log Hides Them All

The search stops at the name, not at a matching function. So declaring any function called log in the derived type hides every base function called log, including ones taking different arguments. Here, base has two versions of log, one taking an int code. Derived redefines only the one without arguments. Now calling log with four hundred four on a derived object fails to build. The compiler counts too many arguments. And the error message doesn't mention hiding at all. It just points at the derived version, which expects no arguments.

The Fix

Restoring Hidden Versions With Using

The natural conclusion is that the base version has been deleted. It hasn't. Naming base explicitly still works: d dot base colon colon log, with four hundred four. It's simply not considered, because the search found the name in derived, and never looked further. To bring the hidden versions back, write using base, colon colon, log, inside the derived type. Now the call with an int finds the base version again. Run it. This isn't overloading. Within one type, same-named functions are considered together. Across an inheritance boundary, the derived declarations are searched first, and alone. Whenever a call that used to work stops compiling after someone adds a derived function, this is the reason.

Extending Base

Call the Base, Then Add More

Often the derived version should do what the base did, and then something more. So call the base version explicitly, and add to it. Here, savings describe first calls account's describe, then prints its own addition. Run it: both lines print. Don't copy the base body into the derived one. A copy won't follow the base when it changes.

Common Mistake

Unqualified Call Recurses Forever

And the qualification matters. Plain describe, inside savings describe, finds itself, and the function calls itself forever. The compiler even says so. It warns that all paths through the function call itself. So I won't run it.

Exact Match

What Replacement Really Requires

For a derived function to replace a base one, rather than merely hide it, the declarations must agree exactly. Same name, same parameter list, and the same const marking. Against a base describe marked const, only an identical declaration replaces it. Leave off the const, and you get a separate function that hides the base one, unusable on a read-only object. Add a parameter, and it's a new function that also hides. Change the capitalization, and the base stays reachable, so nothing seems wrong until the wrong one runs. A different result type is rejected, once the base is marked replaceable. A near miss isn't an error. It quietly produces a second, unrelated function.

Safety Check

Override Turns Near Misses Into Errors

Every one of those near misses compiles. Writing override after the derived declaration turns each into a build error. It states an intention the compiler can verify: this is meant to replace something in the base. A wrong name, a missing const, or an extra parameter are each caught at once. Only the exact match is accepted. Override applies where the base declaration is marked as replaceable, which section 11.5 covers. It never changes what the code does. Its entire job is to reject a signature that doesn't line up. Once you meet that marking, write override on every replacement, without exception.

Surprising Result

Base Handles Give the Base Answer

Here's the part that surprises people. A derived object may be referred to through a base reference, or a base pointer. That's one of the main reasons to arrange types this way. Here, one savings object is reached directly, through an account reference, and through an account pointer. Called directly, it says savings account. But through the reference and the pointer, it says account. Even though the object really is a savings.

Explanation

One Object, Two Answers

One object, two answers. The savings object has both versions inside it. Which one runs depends on the handle. Through a savings variable, the declared type and the object agree, so the savings version runs. Through an account reference or pointer, the version is chosen from the declared type, so the account version runs. And asking by name for account describe runs the base version on purpose. It's decided by the label on the handle, not by what's on the end of it.

Static Binding

Why Collections of Base Pointers Fail

This behavior is called static binding. The version is bound to the call when the program is built, and nothing at run time can change it. For a call on a plain object, it gives exactly what you expect. Through a base handle, it doesn't, and that's a real problem. Hierarchies usually exist to treat kinds uniformly. Keep a collection of base pointers to every kind of account, walk it once, and ask each to describe itself. Under static binding, every call reaches the base version. Run it: three savings accounts, and the collection prints account three times. With fifty accounts it would print account fifty times, and the specialized versions never run. Making the call consult the actual object is the subject of section 11.5.

Troubleshooting

Matching Symptoms to Their Causes

Here are the faults worth recognizing. A base overload that stops compiling after a derived function is added is name hiding. Add a using declaration. The base version running through a base handle is static binding, and its remedy comes in section 11.5. A derived version that never runs, even on a derived object, has a mismatched signature. Compare the declarations, and add override. A program that hangs on the call has a derived version calling itself. Qualify the base call. And when a copied base body drifts out of date, it should have been a call to the base version instead.

Key Terms

Vocabulary for Overriding Behavior

Name hiding is a derived declaration concealing every base function of that name, whatever their parameters. A qualified call names the scope explicitly, to bypass the ordinary search. A using declaration brings hidden base versions back. A signature is the name, parameter list and const marking that must agree for a replacement. Override states that a derived function is meant to replace a base one, so the compiler can check it. And static binding chooses which version a call reaches when the program is built, from the declared type.

Quick Check

Missing Const on the Derived Version

Quick check. The base declares describe, marked const, and the derived declares describe without const. What has the programmer created, and what will they observe? Pause and predict.

Answer

A Hiding Function, Not a Replacement

A separate function that hides the base one, not a replacement. Called on a plain derived object, the derived version runs. Through a base reference, the base version runs. And on a read-only derived object, the call won't even compile, because the hidden base version isn't considered. Remove the comment marks and see.

Key Takeaways

Lookup, Hiding, Signatures, and Binding

To summarize. Name lookup stops at the first scope holding the name. Redefining one function hides all the base versions of that name, and a using declaration brings them back. Extend a base version by calling it with its qualified name. Only an exact signature replaces a base function, and override checks that for you. And through a base handle, static binding runs the base version.

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 Overriding Inherited Behavior

Why did a base class function stop compiling after I added a same-named function to the derived class?
The cause is name hiding. The compiler searches the derived class first and stops at the first function with that name, so one derived log hides every base log, whatever its parameters. The base versions still exist. Add using Base::log; inside the derived class to bring them back, or call d.Base::log(404) explicitly.
How do I call the base class version of a function from inside the derived version?
Write the base class name and the scope operator before the call, then add the extra behavior. For example: void describe() const { Account::describe(); std::cout << "savings\n"; }. Leaving off Account:: is a common mistake, because plain describe() finds itself and recurses forever. Copying the base body instead won't follow later changes to the base.
What does override do in C++, and which mistakes does it catch?
The override keyword states that a derived function is meant to replace a base function marked virtual, so the compiler rejects any mismatch. Without it, a missing const or an extra parameter compiles as a separate function that hides the base one. A misspelled or differently capitalized name compiles as an unrelated function. With override, each of these becomes a build error.
Why does a base class reference run the base version of a function on a derived object?
This is static binding: the compiler picks the version when the program is built, from the declared type of the handle, not the actual object. If Savings s; Account& r = s; and describe prints the class name, s.describe() runs the Savings version but r.describe() runs the Account version. A collection of base pointers therefore calls only base versions.

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 →